blob: e9943e45120404e9492c42b5b9dbbea25e29dd30 [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.settings.common;
import static com.android.settingslib.drawer.CategoryKey.CATEGORY_DEVICE;
import static com.android.settingslib.drawer.TileUtils.META_DATA_KEY_ORDER;
import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_KEYHINT;
import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_SUMMARY;
import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_SUMMARY_URI;
import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_TITLE;
import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_TITLE_URI;
import static java.lang.String.CASE_INSENSITIVE_ORDER;
import android.app.ActivityManager;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.text.TextUtils;
import androidx.annotation.VisibleForTesting;
import androidx.preference.Preference;
import com.android.car.settings.R;
import com.android.car.ui.preference.CarUiPreference;
import com.android.settingslib.drawer.TileUtils;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
/**
* Loads Activity with TileUtils.EXTRA_SETTINGS_ACTION.
*/
// TODO: investigate using SettingsLib Tiles.
public class ExtraSettingsLoader {
static final String META_DATA_PREFERENCE_IS_TOP_LEVEL = "injectedTopLevelPreference";
private static final Logger LOG = new Logger(ExtraSettingsLoader.class);
private static final String META_DATA_PREFERENCE_CATEGORY = "com.android.settings.category";
private final Context mContext;
private final Set<String> mTopLevelCategories;
private final boolean mIsTopLevelSummariesEnabled;
private Map<Preference, Bundle> mPreferenceBundleMap;
private PackageManager mPm;
public ExtraSettingsLoader(Context context) {
mContext = context;
mPm = context.getPackageManager();
mPreferenceBundleMap = new LinkedHashMap<>();
mTopLevelCategories = Set.of(mContext.getResources().getStringArray(
R.array.config_top_level_injection_categories));
mIsTopLevelSummariesEnabled = mContext.getResources().getBoolean(
R.bool.config_top_level_injection_enable_summaries);
}
@VisibleForTesting
void setPackageManager(PackageManager pm) {
mPm = pm;
}
/**
* Returns a map of {@link Preference} and {@link Bundle} representing settings injected from
* system apps and their metadata. The given intent must specify the action to use for
* resolving activities and a category with the key "com.android.settings.category" and one of
* the values in {@link com.android.settingslib.drawer.CategoryKey}.
*
* {@link com.android.settingslib.drawer.TileUtils#EXTRA_SETTINGS_ACTION} is automatically added
* for backwards compatibility. Please make sure to use
* {@link com.android.settingslib.drawer.TileUtils#IA_SETTINGS_ACTION} instead.
*
* @param intent intent specifying the extra settings category to load
*/
public Map<Preference, Bundle> loadPreferences(Intent intent) {
intent.setAction(TileUtils.IA_SETTINGS_ACTION);
List<ResolveInfo> results = mPm.queryIntentActivitiesAsUser(intent,
PackageManager.GET_META_DATA, ActivityManager.getCurrentUser());
intent.setAction(TileUtils.EXTRA_SETTINGS_ACTION);
List<ResolveInfo> extra_settings_results = mPm.queryIntentActivitiesAsUser(intent,
PackageManager.GET_META_DATA, ActivityManager.getCurrentUser());
for (ResolveInfo extra_settings_resolveInfo : extra_settings_results) {
if (!results.contains(extra_settings_resolveInfo)) {
results.add(extra_settings_resolveInfo);
}
}
String extraCategory = intent.getStringExtra(META_DATA_PREFERENCE_CATEGORY);
// Filter to only include valid results and then sort the results
// Filter criteria: must be a system application and must have metaData
// Sort criteria: sort results based on [order, package within order]
results = results.stream()
.filter(r -> r.system && r.activityInfo != null && r.activityInfo.metaData != null)
.sorted((r1, r2) -> {
// First sort by order
int orderCompare = r2.activityInfo.metaData.getInt(META_DATA_KEY_ORDER)
- r1.activityInfo.metaData.getInt(META_DATA_KEY_ORDER);
if (orderCompare != 0) {
return orderCompare;
}
// Then sort by package name
String package1 = r1.activityInfo.packageName;
String package2 = r2.activityInfo.packageName;
return CASE_INSENSITIVE_ORDER.compare(package1, package2);
})
.collect(Collectors.toList());
for (ResolveInfo resolved : results) {
String key = null;
String title = null;
String summary = null;
String category = null;
ActivityInfo activityInfo = resolved.activityInfo;
Bundle metaData = activityInfo.metaData;
try {
Resources res = mPm.getResourcesForApplication(activityInfo.packageName);
if (metaData.containsKey(META_DATA_PREFERENCE_KEYHINT)) {
key = extractMetaDataString(metaData, META_DATA_PREFERENCE_KEYHINT, res);
}
if (!metaData.containsKey(META_DATA_PREFERENCE_TITLE_URI)) {
title = extractMetaDataString(metaData, META_DATA_PREFERENCE_TITLE, res);
if (TextUtils.isEmpty(title)) {
LOG.d("no title.");
title = activityInfo.loadLabel(mPm).toString();
}
}
if (!metaData.containsKey(META_DATA_PREFERENCE_SUMMARY_URI)) {
summary = extractMetaDataString(metaData, META_DATA_PREFERENCE_SUMMARY, res);
if (TextUtils.isEmpty(summary)) {
LOG.d("no description.");
}
}
category = extractMetaDataString(metaData, META_DATA_PREFERENCE_CATEGORY, res);
if (TextUtils.isEmpty(category)) {
LOG.d("no category.");
}
} catch (PackageManager.NameNotFoundException | Resources.NotFoundException e) {
LOG.d("Couldn't find info", e);
}
Intent extraSettingIntent =
new Intent().setClassName(activityInfo.packageName, activityInfo.name);
if (category == null) {
// If category is not specified or not supported, default to device.
category = CATEGORY_DEVICE;
}
boolean isTopLevel = mTopLevelCategories.contains(category);
metaData.putBoolean(META_DATA_PREFERENCE_IS_TOP_LEVEL, isTopLevel);
Drawable icon = ExtraSettingsUtil.createIcon(mContext, metaData,
activityInfo.packageName);
if (!TextUtils.equals(extraCategory, category)) {
continue;
}
CarUiPreference preference;
if (isTopLevel) {
preference = new TopLevelPreference(mContext);
if (!mIsTopLevelSummariesEnabled) {
// remove summary data
summary = null;
metaData.remove(META_DATA_PREFERENCE_SUMMARY_URI);
}
} else {
preference = new CarUiPreference(mContext);
}
preference.setTitle(title);
preference.setSummary(summary);
if (key != null) {
preference.setKey(key);
}
if (icon != null) {
preference.setIcon(icon);
}
preference.setIntent(extraSettingIntent);
mPreferenceBundleMap.put(preference, metaData);
}
return mPreferenceBundleMap;
}
/**
* Extracts the value in the metadata specified by the key.
* If it is resource, resolve the string and return. Otherwise, return the string itself.
*/
private String extractMetaDataString(Bundle metaData, String key, Resources res) {
if (metaData.containsKey(key)) {
if (metaData.get(key) instanceof Integer) {
return res.getString(metaData.getInt(key));
}
return metaData.getString(key);
}
return null;
}
}