blob: c387e9dd84f625c73c3a5b70d3123f0149749cf5 [file] [log] [blame]
/*
* Copyright (C) 2017 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.settings.intelligence.suggestions.model;
import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.res.Resources;
import android.graphics.drawable.Icon;
import android.net.Uri;
import android.os.Bundle;
import android.service.settings.suggestions.Suggestion;
import androidx.annotation.VisibleForTesting;
import android.text.TextUtils;
import android.util.Log;
import com.android.settings.intelligence.suggestions.eligibility.AccountEligibilityChecker;
import com.android.settings.intelligence.suggestions.eligibility.AutomotiveEligibilityChecker;
import com.android.settings.intelligence.suggestions.eligibility.ConnectivityEligibilityChecker;
import com.android.settings.intelligence.suggestions.eligibility.DismissedChecker;
import com.android.settings.intelligence.suggestions.eligibility.FeatureEligibilityChecker;
import com.android.settings.intelligence.suggestions.eligibility.ProviderEligibilityChecker;
import java.util.List;
/**
* A wrapper to {@link android.content.pm.ResolveInfo} that matches Suggestion signature.
* <p/>
* This class contains necessary metadata to eventually be
* processed into a {@link android.service.settings.suggestions.Suggestion}.
*/
public class CandidateSuggestion {
private static final String TAG = "CandidateSuggestion";
/**
* Name of the meta-data item that should be set in the AndroidManifest.xml
* to specify the title text that should be displayed for the preference.
*/
@VisibleForTesting
public static final String META_DATA_PREFERENCE_TITLE = "com.android.settings.title";
/**
* Name of the meta-data item that should be set in the AndroidManifest.xml
* to specify the summary text that should be displayed for the preference.
*/
@VisibleForTesting
public static final String META_DATA_PREFERENCE_SUMMARY = "com.android.settings.summary";
/**
* Name of the meta-data item that should be set in the AndroidManifest.xml
* to specify the content provider providing the summary text that should be displayed for the
* preference.
*
* Summary provided by the content provider overrides any static summary.
*/
@VisibleForTesting
public static final String META_DATA_PREFERENCE_SUMMARY_URI =
"com.android.settings.summary_uri";
/**
* Name of the meta-data item that should be set in the AndroidManifest.xml
* to specify the icon that should be displayed for the preference.
*/
@VisibleForTesting
public static final String META_DATA_PREFERENCE_ICON = "com.android.settings.icon";
/**
* Hint for type of suggestion UI to be displayed.
*/
@VisibleForTesting
public static final String META_DATA_PREFERENCE_CUSTOM_VIEW =
"com.android.settings.custom_view";
private final String mId;
private final Context mContext;
private final ResolveInfo mResolveInfo;
private final ComponentName mComponent;
private final Intent mIntent;
private final boolean mIsEligible;
private final boolean mIgnoreAppearRule;
public CandidateSuggestion(Context context, ResolveInfo resolveInfo,
boolean ignoreAppearRule) {
mContext = context;
mIgnoreAppearRule = ignoreAppearRule;
mResolveInfo = resolveInfo;
mIntent = new Intent().setClassName(
resolveInfo.activityInfo.packageName, resolveInfo.activityInfo.name);
mComponent = mIntent.getComponent();
mId = generateId();
mIsEligible = initIsEligible();
}
public String getId() {
return mId;
}
public ComponentName getComponent() {
return mComponent;
}
/**
* Whether or not this candidate is eligible for display.
* <p/>
* Note: eligible doesn't mean it will be displayed.
*/
public boolean isEligible() {
return mIsEligible;
}
public Suggestion toSuggestion() {
if (!mIsEligible) {
return null;
}
final Suggestion.Builder builder = new Suggestion.Builder(mId);
updateBuilder(builder);
return builder.build();
}
/**
* Checks device condition against suggestion requirement. Returns true if the suggestion is
* eligible.
* <p/>
* Note: eligible doesn't mean it will be displayed.
*/
private boolean initIsEligible() {
if (!ProviderEligibilityChecker.isEligible(mContext, mId, mResolveInfo)) {
return false;
}
if (!ConnectivityEligibilityChecker.isEligible(mContext, mId, mResolveInfo)) {
return false;
}
if (!FeatureEligibilityChecker.isEligible(mContext, mId, mResolveInfo)) {
return false;
}
if (!AccountEligibilityChecker.isEligible(mContext, mId, mResolveInfo)) {
return false;
}
if (!DismissedChecker.isEligible(mContext, mId, mResolveInfo, mIgnoreAppearRule)) {
return false;
}
if (!AutomotiveEligibilityChecker.isEligible(mContext, mId, mResolveInfo)) {
return false;
}
return true;
}
private void updateBuilder(Suggestion.Builder builder) {
final PackageManager pm = mContext.getPackageManager();
final String packageName = mComponent.getPackageName();
int iconRes = 0;
int flags = 0;
CharSequence title = null;
CharSequence summary = null;
Icon icon = null;
// Get the activity's meta-data
try {
final Resources res = pm.getResourcesForApplication(packageName);
final Bundle metaData = mResolveInfo.activityInfo.metaData;
if (res != null && metaData != null) {
// First get override data
final Bundle overrideData = getOverrideData(metaData);
// Get icon
if (metaData.containsKey(META_DATA_PREFERENCE_ICON)) {
iconRes = metaData.getInt(META_DATA_PREFERENCE_ICON);
} else {
iconRes = mResolveInfo.activityInfo.icon;
}
if (iconRes != 0) {
icon = Icon.createWithResource(
mResolveInfo.activityInfo.packageName, iconRes);
}
// Get title
title = getStringFromBundle(overrideData, META_DATA_PREFERENCE_TITLE);
if (TextUtils.isEmpty(title) && metaData.containsKey(META_DATA_PREFERENCE_TITLE)) {
if (metaData.get(META_DATA_PREFERENCE_TITLE) instanceof Integer) {
title = res.getString(metaData.getInt(META_DATA_PREFERENCE_TITLE));
} else {
title = metaData.getString(META_DATA_PREFERENCE_TITLE);
}
}
// Get summary
summary = getStringFromBundle(overrideData, META_DATA_PREFERENCE_SUMMARY);
if (TextUtils.isEmpty(summary)
&& metaData.containsKey(META_DATA_PREFERENCE_SUMMARY)) {
if (metaData.get(META_DATA_PREFERENCE_SUMMARY) instanceof Integer) {
summary = res.getString(metaData.getInt(META_DATA_PREFERENCE_SUMMARY));
} else {
summary = metaData.getString(META_DATA_PREFERENCE_SUMMARY);
}
}
// Detect remote view
flags = metaData.containsKey(META_DATA_PREFERENCE_CUSTOM_VIEW)
? Suggestion.FLAG_HAS_BUTTON : 0;
}
} catch (PackageManager.NameNotFoundException | Resources.NotFoundException e) {
Log.w(TAG, "Couldn't find info", e);
}
// Set the preference title to the activity's label if no
// meta-data is found
if (TextUtils.isEmpty(title)) {
title = mResolveInfo.activityInfo.loadLabel(pm);
}
builder.setTitle(title)
.setSummary(summary)
.setFlags(flags)
.setIcon(icon)
.setPendingIntent(PendingIntent
.getActivity(mContext, 0 /* requestCode */, mIntent, 0 /* flags */));
}
/**
* Extracts a string from bundle.
*/
private CharSequence getStringFromBundle(Bundle bundle, String key) {
if (bundle == null || TextUtils.isEmpty(key)) {
return null;
}
return bundle.getString(key);
}
private Bundle getOverrideData(Bundle metadata) {
if (metadata == null || !metadata.containsKey(META_DATA_PREFERENCE_SUMMARY_URI)) {
Log.d(TAG, "Metadata null or has no info about summary_uri");
return null;
}
final String uriString = metadata.getString(META_DATA_PREFERENCE_SUMMARY_URI);
final Bundle bundle = getBundleFromUri(uriString);
return bundle;
}
/**
* Calls method through ContentProvider and expects a bundle in return.
*/
private Bundle getBundleFromUri(String uriString) {
final Uri uri = Uri.parse(uriString);
final String method = getMethodFromUri(uri);
if (TextUtils.isEmpty(method)) {
return null;
}
try {
return mContext.getContentResolver().call(uri, method, null /* args */,
null /* bundle */);
} catch (IllegalArgumentException e){
Log.d(TAG, "Unknown summary_uri", e);
return null;
}
}
/**
* Returns the first path segment of the uri if it exists as the method, otherwise null.
*/
private String getMethodFromUri(Uri uri) {
if (uri == null) {
return null;
}
final List<String> pathSegments = uri.getPathSegments();
if ((pathSegments == null) || pathSegments.isEmpty()) {
return null;
}
return pathSegments.get(0);
}
private String generateId() {
return mComponent.flattenToString();
}
}