blob: dcf7c9330e39d564034d7b7a5faafc4fb27badee [file] [log] [blame]
/*
* Copyright (C) 2023 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 android.car.settings.CarSettings.Secure.KEY_PACKAGES_DISABLED_ON_RESOURCE_OVERUSE;
import static android.car.settings.CarSettings.Secure.KEY_UNACCEPTED_TOS_DISABLED_APPS;
import static android.car.settings.CarSettings.Secure.KEY_USER_TOS_ACCEPTED;
import static com.android.car.carlauncher.hidden.HiddenApiAccess.hasBaseUserRestriction;
import static java.lang.annotation.RetentionPolicy.SOURCE;
import android.app.Activity;
import android.app.ActivityManager;
import android.app.ActivityOptions;
import android.app.admin.DevicePolicyManager;
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.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.LauncherActivityInfo;
import android.content.pm.LauncherApps;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.net.Uri;
import android.os.Process;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
import android.service.media.MediaBrowserService;
import android.text.TextUtils;
import android.util.ArraySet;
import android.util.Log;
import android.util.Pair;
import android.view.View;
import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.car.dockutil.events.DockEventSenderHelper;
import com.android.car.dockutil.shortcuts.PinShortcutItem;
import com.android.car.media.common.source.MediaSourceUtil;
import com.android.car.ui.shortcutspopup.CarUiShortcutsPopup;
import com.google.common.collect.Sets;
import java.lang.annotation.Retention;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.Consumer;
/**
* Util class that contains helper method used by app launcher classes.
*/
public class AppLauncherUtils {
private static final String TAG = "AppLauncherUtils";
private static final String ANDROIDX_CAR_APP_LAUNCHABLE = "androidx.car.app.launchable";
@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;
// This value indicates if TOS has not been accepted by the user
private static final String TOS_NOT_ACCEPTED = "1";
// This value indicates if TOS is in uninitialized state
private static final String TOS_UNINITIALIZED = "0";
static final String TOS_DISABLED_APPS_SEPARATOR = ",";
static final String PACKAGES_DISABLED_ON_RESOURCE_OVERUSE_SEPARATOR = ";";
// Max no. of uses tags in automotiveApp XML. This is an arbitrary limit to be defensive
// to bad input.
private static final int MAX_APP_TYPES = 64;
private static final String PACKAGE_URI_PREFIX = "package:";
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);
/**
* Helper method that launches the app given the app's AppMetaData.
*/
public static void launchApp(Context context, Intent intent) {
ActivityOptions options = ActivityOptions.makeBasic();
options.setLaunchDisplayId(context.getDisplay().getDisplayId());
context.startActivity(intent, options.toBundle());
}
/** 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> launchablesMap,
@NonNull Map<ComponentName, ResolveInfo> mediaServices) {
mLaunchables = launchablesMap;
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());
}
/** Returns list of Media Services for the launcher **/
@NonNull
Map<ComponentName, ResolveInfo> getMediaServices() {
return mMediaServices;
}
}
private static final 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 appsToHide 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
* of such apps are always excluded.
* @param carMediaManager The {@link CarMediaManager} system service
* @return a new {@link LauncherAppsInfo}
*/
@NonNull
static LauncherAppsInfo 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) {
if (launcherApps == null || carPackageManager == null || packageManager == null
|| carMediaManager == null) {
return EMPTY_APPS_INFO;
}
boolean isDockEnabled = context.getResources().getBoolean(R.bool.config_enableDock);
// Using new list since we require a mutable list to do removeIf.
List<ResolveInfo> mediaServices = new ArrayList<>();
mediaServices.addAll(
packageManager.queryIntentServices(
new Intent(MediaBrowserService.SERVICE_INTERFACE),
PackageManager.GET_RESOLVED_FILTER));
List<LauncherActivityInfo> availableActivities =
launcherApps.getActivityList(null, Process.myUserHandle());
int launchablesSize = mediaServices.size() + availableActivities.size();
Map<ComponentName, AppMetaData> launchablesMap = new HashMap<>(launchablesSize);
Map<ComponentName, ResolveInfo> mediaServicesMap = new HashMap<>(mediaServices.size());
Set<String> mEnabledPackages = new ArraySet<>(launchablesSize);
Set<String> tosDisabledPackages = getTosDisabledPackages(context);
Set<String> customMediaComponents = Sets.newHashSet(
context.getResources().getStringArray(
com.android.car.media.common.R.array.custom_media_packages));
// 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);
mEnabledPackages.add(packageName);
if (shouldAddToLaunchables(context, componentName, appsToHide,
customMediaComponents, appTypes, APP_TYPE_MEDIA_SERVICES)) {
CharSequence displayName = info.serviceInfo.loadLabel(packageManager);
AppMetaData appMetaData = new AppMetaData(
displayName,
componentName,
info.serviceInfo.loadIcon(packageManager),
/* isDistractionOptimized= */ true,
/* isMirroring = */ false,
/* isDisabledByTos= */ tosDisabledPackages.contains(packageName),
contextArg -> {
if (openMediaCenter) {
AppLauncherUtils.launchApp(contextArg,
createMediaLaunchIntent(componentName));
} else {
selectMediaSourceAndFinish(contextArg, componentName,
carMediaManager);
}
},
buildShortcuts(componentName, displayName, shortcutsListener,
isDockEnabled));
launchablesMap.put(componentName, appMetaData);
}
}
}
// Process activities
if ((appTypes & APP_TYPE_LAUNCHABLES) != 0) {
for (LauncherActivityInfo info : availableActivities) {
ComponentName componentName = info.getComponentName();
mEnabledPackages.add(componentName.getPackageName());
if (shouldAddToLaunchables(context, componentName, appsToHide,
customMediaComponents, appTypes, APP_TYPE_LAUNCHABLES)) {
boolean isDistractionOptimized =
isActivityDistractionOptimized(carPackageManager,
componentName.getPackageName(), info.getName());
boolean isDisabledByTos = tosDisabledPackages
.contains(componentName.getPackageName());
CharSequence displayName = info.getLabel();
boolean isMirroring = componentName.getPackageName()
.equals(mirroringAppPkgName);
AppMetaData appMetaData = new AppMetaData(
displayName,
componentName,
info.getBadgedIcon(0),
isDistractionOptimized,
isMirroring,
isDisabledByTos,
contextArg -> {
if (componentName.getPackageName().equals(mirroringAppPkgName)) {
Log.d(TAG, "non-media service package name "
+ "equals mirroring pkg name");
}
AppLauncherUtils.launchApp(contextArg,
isMirroring ? mirroringAppRedirect :
createAppLaunchIntent(componentName));
},
buildShortcuts(componentName, displayName, shortcutsListener,
isDockEnabled));
launchablesMap.put(componentName, appMetaData);
}
}
List<ResolveInfo> disabledActivities = getDisabledActivities(context, packageManager,
mEnabledPackages);
for (ResolveInfo info : disabledActivities) {
String packageName = info.activityInfo.packageName;
String className = info.activityInfo.name;
ComponentName componentName = new ComponentName(packageName, className);
if (!shouldAddToLaunchables(context, componentName, appsToHide,
customMediaComponents, appTypes, APP_TYPE_LAUNCHABLES)) {
continue;
}
boolean isDistractionOptimized =
isActivityDistractionOptimized(carPackageManager, packageName, className);
boolean isDisabledByTos = tosDisabledPackages.contains(packageName);
CharSequence displayName = info.activityInfo.loadLabel(packageManager);
AppMetaData appMetaData = new AppMetaData(
displayName,
componentName,
info.activityInfo.loadIcon(packageManager),
isDistractionOptimized,
/* isMirroring = */ false,
isDisabledByTos,
contextArg -> {
packageManager.setApplicationEnabledSetting(packageName,
PackageManager.COMPONENT_ENABLED_STATE_ENABLED, 0);
// Fetch the current enabled setting to make sure the setting is synced
// before launching the activity. Otherwise, the activity may not
// launch.
if (packageManager.getApplicationEnabledSetting(packageName)
!= PackageManager.COMPONENT_ENABLED_STATE_ENABLED) {
throw new IllegalStateException(
"Failed to enable the disabled package [" + packageName
+ "]");
}
Log.i(TAG, "Successfully enabled package [" + packageName + "]");
AppLauncherUtils.launchApp(contextArg,
createAppLaunchIntent(componentName));
},
buildShortcuts(componentName, displayName, shortcutsListener,
isDockEnabled));
launchablesMap.put(componentName, appMetaData);
}
List<ResolveInfo> restrictedActivities = getTosDisabledActivities(
context,
packageManager,
mEnabledPackages
);
for (ResolveInfo info: restrictedActivities) {
String packageName = info.activityInfo.packageName;
String className = info.activityInfo.name;
ComponentName componentName = new ComponentName(packageName, className);
boolean isDistractionOptimized =
isActivityDistractionOptimized(carPackageManager, packageName, className);
boolean isDisabledByTos = tosDisabledPackages.contains(packageName);
AppMetaData appMetaData = new AppMetaData(
info.activityInfo.loadLabel(packageManager),
componentName,
info.activityInfo.loadIcon(packageManager),
isDistractionOptimized,
/* isMirroring = */ false,
isDisabledByTos,
contextArg -> {
Intent tosIntent = getIntentForTosAcceptanceFlow(contextArg);
launchApp(contextArg, tosIntent);
},
null
);
launchablesMap.put(componentName, appMetaData);
}
}
return new LauncherAppsInfo(launchablesMap, mediaServicesMap);
}
/**
* Gets the intent for launching the TOS acceptance flow
*
* @param context The app context
* @return TOS intent, or null
*/
@Nullable
public static Intent getIntentForTosAcceptanceFlow(Context context) {
String tosIntentName =
context.getResources().getString(R.string.user_tos_activity_intent);
try {
return Intent.parseUri(tosIntentName, Intent.URI_ANDROID_APP_SCHEME);
} catch (URISyntaxException se) {
Log.e(TAG, "Invalid intent URI in user_tos_activity_intent", se);
return null;
}
}
private static Consumer<Pair<Context, View>> buildShortcuts(
ComponentName componentName, CharSequence displayName,
ShortcutsListener shortcutsListener, boolean isDockEnabled) {
return pair -> {
CarUiShortcutsPopup.Builder carUiShortcutsPopupBuilder =
new CarUiShortcutsPopup.Builder()
.addShortcut(buildForceStopShortcut(componentName.getPackageName(),
displayName, pair.first, shortcutsListener))
.addShortcut(buildAppInfoShortcut(componentName.getPackageName(),
pair.first));
if (isDockEnabled) {
carUiShortcutsPopupBuilder
.addShortcut(buildPinToDockShortcut(componentName, pair.first));
}
CarUiShortcutsPopup carUiShortcutsPopup = carUiShortcutsPopupBuilder
.build(pair.first, pair.second);
carUiShortcutsPopup.show();
shortcutsListener.onShortcutsShow(carUiShortcutsPopup);
};
}
private static CarUiShortcutsPopup.ShortcutItem buildForceStopShortcut(String packageName,
CharSequence displayName,
Context context,
ShortcutsListener shortcutsListener) {
return new CarUiShortcutsPopup.ShortcutItem() {
@Override
public CarUiShortcutsPopup.ItemData data() {
return new CarUiShortcutsPopup.ItemData(
R.drawable.ic_force_stop_caution_icon,
context.getResources().getString(
R.string.app_launcher_stop_app_action));
}
@Override
public boolean onClick() {
shortcutsListener.onShortcutsItemClick(packageName, displayName,
/* allowStopApp= */ true);
return true;
}
@Override
public boolean isEnabled() {
return shouldAllowStopApp(packageName, context);
}
};
}
private static CarUiShortcutsPopup.ShortcutItem buildAppInfoShortcut(String packageName,
Context context) {
return new CarUiShortcutsPopup.ShortcutItem() {
@Override
public CarUiShortcutsPopup.ItemData data() {
return new CarUiShortcutsPopup.ItemData(
R.drawable.ic_app_info,
context.getResources().getString(
R.string.app_launcher_app_info_action));
}
@Override
public boolean onClick() {
Uri packageURI = Uri.parse(PACKAGE_URI_PREFIX + packageName);
Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
packageURI);
context.startActivity(intent);
return true;
}
@Override
public boolean isEnabled() {
return true;
}
};
}
private static CarUiShortcutsPopup.ShortcutItem buildPinToDockShortcut(
ComponentName componentName, Context context) {
DockEventSenderHelper mHelper = new DockEventSenderHelper(context);
return new PinShortcutItem(context.getResources(), /* isItemPinned= */ false,
/* pinItemClickDelegate= */ () -> mHelper.sendPinEvent(componentName),
/* unpinItemClickDelegate= */ () -> mHelper.sendUnpinEvent(componentName)
);
}
/**
* Force stops an app
* <p>Note: Uses hidden apis<p/>
*/
public static void forceStop(String packageName, Context context, CharSequence displayName,
CarMediaManager carMediaManager, Map<ComponentName, ResolveInfo> mediaServices,
ShortcutsListener listener) {
ActivityManager activityManager = context.getSystemService(ActivityManager.class);
if (activityManager != null) {
maybeReplaceMediaSource(carMediaManager, packageName, mediaServices,
CarMediaManager.MEDIA_SOURCE_MODE_BROWSE);
maybeReplaceMediaSource(carMediaManager, packageName, mediaServices,
CarMediaManager.MEDIA_SOURCE_MODE_PLAYBACK);
activityManager.forceStopPackage(packageName);
String message = context.getResources()
.getString(R.string.app_launcher_stop_app_success_toast_text, displayName);
listener.onStopAppSuccess(message);
}
}
private static boolean isCurrentMediaSource(CarMediaManager carMediaManager,
String packageName, int mode) {
ComponentName componentName = carMediaManager.getMediaSource(mode);
if (componentName == null) {
//There is no current media source.
return false;
}
return Objects.equals(componentName.getPackageName(), packageName);
}
/***
* Updates the MediaSource to second most recent if {@code packageName} is current media source
* Sets to MediaSource to null if no previous MediaSource exists.
*/
private static void maybeReplaceMediaSource(CarMediaManager carMediaManager, String packageName,
Map<ComponentName, ResolveInfo> allMediaServices,
int mode) {
if (!isCurrentMediaSource(carMediaManager, packageName, mode)) {
return;
}
//find the most recent source from history not equal to force-stopping package.
List<ComponentName> mediaSources = carMediaManager.getLastMediaSources(mode);
ComponentName componentName = mediaSources.stream().filter(c-> (!c.getPackageName()
.equals(packageName))).findFirst().orElse(null);
if (componentName == null) {
//no recent package found, find from all available media services.
componentName = allMediaServices.keySet().stream().filter(
c -> (!c.getPackageName().equals(packageName))).findFirst().orElse(null);
if (componentName == null) {
Log.e(TAG, "Stop-app, no alternative media service found");
}
}
carMediaManager.setMediaSource(componentName, mode);
}
/**
* <p>Note: Uses hidden apis<p/>
* @return true if the user has restrictions to force stop an app with {@code appInfo}
*/
private static boolean hasUserRestriction(ApplicationInfo appInfo, Context context) {
String restriction = UserManager.DISALLOW_APPS_CONTROL;
UserManager userManager = context.getSystemService(UserManager.class);
if (userManager == null) {
Log.e(TAG, " Disabled because , UserManager is null");
return true;
}
if (!userManager.hasUserRestriction(restriction)) {
return false;
}
UserHandle user = UserHandle.getUserHandleForUid(appInfo.uid);
if (hasBaseUserRestriction(userManager, restriction, user)) {
Log.d(TAG, " Disabled because " + user + " has " + restriction
+ " restriction");
return true;
}
// Not disabled for this User
return false;
}
/**
* <p>Note: uses hidden apis</p>
*
* @param packageName name of the package to stop the app
* @param context app context
* @return true if an app should show the Stop app action
*/
private static boolean shouldAllowStopApp(String packageName, Context context) {
DevicePolicyManager dm = context.getSystemService(DevicePolicyManager.class);
if (dm == null || dm.packageHasActiveAdmins(packageName)) {
return false;
}
try {
ApplicationInfo appInfo = context.getPackageManager().getApplicationInfo(packageName,
PackageManager.ApplicationInfoFlags.of(PackageManager.GET_META_DATA));
// Show only if the User has no restrictions to force stop this app
if (hasUserRestriction(appInfo, context)) {
return false;
}
// Show only if the app is running
if ((appInfo.flags & ApplicationInfo.FLAG_STOPPED) == 0) {
return true;
}
} catch (PackageManager.NameNotFoundException e) {
Log.d(TAG, "shouldAllowStopApp() Package " + packageName + " was not found");
}
return false;
}
private static List<ResolveInfo> getDisabledActivities(Context context,
PackageManager packageManager, Set<String> enabledPackages) {
return getActivitiesFromSystemPreferences(
context,
packageManager,
enabledPackages,
KEY_PACKAGES_DISABLED_ON_RESOURCE_OVERUSE,
PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS,
PACKAGES_DISABLED_ON_RESOURCE_OVERUSE_SEPARATOR);
}
private static List<ResolveInfo> getTosDisabledActivities(
Context context,
PackageManager packageManager,
Set<String> enabledPackages) {
return getActivitiesFromSystemPreferences(
context,
packageManager,
enabledPackages,
KEY_UNACCEPTED_TOS_DISABLED_APPS,
PackageManager.MATCH_DISABLED_COMPONENTS,
TOS_DISABLED_APPS_SEPARATOR);
}
/**
* Get a list of activities from packages in system preferences by key
* @param context the app context
* @param packageManager The PackageManager
* @param enabledPackages Set of packages enabled by system
* @param settingsKey Key to read from system preferences
* @param sep Separator
*
* @return List of activities read from system preferences
*/
private static List<ResolveInfo> getActivitiesFromSystemPreferences(
Context context,
PackageManager packageManager,
Set<String> enabledPackages,
String settingsKey,
int filter,
String sep) {
ContentResolver contentResolverForUser = context.createContextAsUser(
UserHandle.getUserHandleForUid(Process.myUid()), /* flags= */ 0)
.getContentResolver();
String settingsValue = Settings.Secure.getString(contentResolverForUser, settingsKey);
Set<String> packages = TextUtils.isEmpty(settingsValue) ? new ArraySet<>()
: new ArraySet<>(Arrays.asList(settingsValue.split(
sep)));
if (packages.isEmpty()) {
return Collections.emptyList();
}
List<ResolveInfo> allActivities = packageManager.queryIntentActivities(
new Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_LAUNCHER),
PackageManager.ResolveInfoFlags.of(PackageManager.GET_RESOLVED_FILTER
| filter));
List<ResolveInfo> activities = new ArrayList<>();
for (int i = 0; i < allActivities.size(); ++i) {
ResolveInfo info = allActivities.get(i);
if (!enabledPackages.contains(info.activityInfo.packageName)
&& packages.contains(info.activityInfo.packageName)) {
activities.add(info);
}
}
return activities;
}
private static boolean shouldAddToLaunchables(Context context,
@NonNull ComponentName componentName,
@NonNull Set<String> appsToHide,
@NonNull Set<String> customMediaComponents,
@AppTypes int appTypesToShow,
@AppTypes int componentAppType) {
if (appsToHide.contains(componentName.getPackageName())) {
return false;
}
switch (componentAppType) {
// Process media services
case APP_TYPE_MEDIA_SERVICES:
// For a media service in customMediaComponents, if its application's launcher
// activity will be shown in the Launcher, don't show the service's icon in the
// Launcher.
if (customMediaComponents.contains(componentName.flattenToString())) {
if ((appTypesToShow & APP_TYPE_LAUNCHABLES) != 0) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "MBS for custom media app " + componentName
+ " is skipped in app launcher");
}
return false;
}
// Media switcher use case should still show
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "MBS for custom media app " + componentName
+ " is included in media switcher");
}
return true;
}
// Only Keep MBS that is a media template
return new MediaSourceUtil(context).isMediaTemplate(componentName);
// Process activities
case APP_TYPE_LAUNCHABLES:
return true;
default:
Log.e(TAG, "Invalid componentAppType : " + componentAppType);
return false;
}
}
private static void selectMediaSourceAndFinish(Context context, ComponentName componentName,
CarMediaManager carMediaManager) {
try {
carMediaManager.setMediaSource(componentName, CarMediaManager.MEDIA_SOURCE_MODE_BROWSE);
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;
}
/**
* Callback when a ShortcutsPopup View is shown
*/
protected interface ShortcutsListener {
void onShortcutsShow(CarUiShortcutsPopup carUiShortcutsPopup);
void onShortcutsItemClick(String packageName, CharSequence displayName,
boolean allowStopApp);
void onStopAppSuccess(String message);
}
/**
* Returns a set of packages that are disabled by tos
*
* @param context The application context
* @return Set of packages disabled by tos
*/
public static Set<String> getTosDisabledPackages(Context context) {
ContentResolver contentResolverForUser = context.createContextAsUser(
UserHandle.getUserHandleForUid(Process.myUid()), /* flags= */ 0)
.getContentResolver();
String settingsValue = Settings.Secure.getString(contentResolverForUser,
KEY_UNACCEPTED_TOS_DISABLED_APPS);
return TextUtils.isEmpty(settingsValue) ? new ArraySet<>()
: new ArraySet<>(Arrays.asList(settingsValue.split(
TOS_DISABLED_APPS_SEPARATOR)));
}
private static Intent createMediaLaunchIntent(ComponentName componentName) {
return new Intent(Car.CAR_INTENT_ACTION_MEDIA_TEMPLATE)
.putExtra(Car.CAR_EXTRA_MEDIA_COMPONENT, componentName.flattenToString());
}
private static Intent createAppLaunchIntent(ComponentName componentName) {
return new Intent(Intent.ACTION_MAIN)
.setComponent(componentName)
.addCategory(Intent.CATEGORY_LAUNCHER)
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
}
/**
* Check if the tos banner has to be displayed
* @param context The application context
* @return true if the banner needs to be displayed, false otherwise
*/
static boolean showTosBanner(Context context) {
// TODO (b/277235742): Add backoff strategy to dismiss banner
return !tosAccepted(context);
}
/**
* Check if a user has accepted TOS
*
* @param context The application context
* @return true if the user has accepted Tos, false otherwise
*/
public static boolean tosAccepted(Context context) {
ContentResolver contentResolverForUser = context.createContextAsUser(
UserHandle.getUserHandleForUid(Process.myUid()), /* flags= */ 0)
.getContentResolver();
String settingsValue = Settings.Secure.getString(
contentResolverForUser,
KEY_USER_TOS_ACCEPTED);
return !Objects.equals(settingsValue, TOS_NOT_ACCEPTED);
}
/**
* Check if TOS status is uninitialized
*
* @param context The application context
*
* @return true if tos is uninitialized, false otherwise
*/
static boolean tosStatusUninitialized(Context context) {
ContentResolver contentResolverForUser = context.createContextAsUser(
UserHandle.getUserHandleForUid(Process.myUid()), /* flags= */ 0)
.getContentResolver();
String settingsValue = Settings.Secure.getString(
contentResolverForUser,
KEY_USER_TOS_ACCEPTED);
return Objects.equals(settingsValue, TOS_UNINITIALIZED);
}
}