Files
app_Settings/src/com/android/settings/notification/zen/ZenRuleSelectionDialog.java
Yuri Lin afe52dfae5 Disable zen rule preferences with invalid activities
While resolveActivity is used to determine whether an Intent can be handled by something, this doesn't catch the case of explicit intents whose activity class doesn't exist. Here we check for it through PackageManager.queryIntentActivities instead for existing zen rules (if they were added when the activity exists, but it no longer does).

For new rules, check the validity of the activity for external rules before adding them to the list.

Bug: 238144390
Test: manual via DND app
Change-Id: Ia920ca792f9c17a5d684baf877c882ce7fadffd6
2022-12-14 14:13:36 -05:00

265 lines
10 KiB
Java

/*
* Copyright (C) 2015 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.notification.zen;
import android.app.Dialog;
import android.app.NotificationManager;
import android.app.settings.SettingsEnums;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.ComponentInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.graphics.drawable.Drawable;
import android.os.AsyncTask;
import android.os.Bundle;
import android.service.notification.ZenModeConfig;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.Fragment;
import com.android.settings.R;
import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
import com.android.settings.utils.ZenServiceListing;
import java.lang.ref.WeakReference;
import java.text.Collator;
import java.util.Comparator;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
public class ZenRuleSelectionDialog extends InstrumentedDialogFragment {
private static final String TAG = "ZenRuleSelectionDialog";
private static final boolean DEBUG = ZenModeSettings.DEBUG;
private static ZenServiceListing mServiceListing;
protected static PositiveClickListener mPositiveClickListener;
private static Context mContext;
private static PackageManager mPm;
private static NotificationManager mNm;
private LinearLayout mRuleContainer;
/**
* The interface we expect a listener to implement.
*/
public interface PositiveClickListener {
void onSystemRuleSelected(ZenRuleInfo ruleInfo, Fragment parent);
void onExternalRuleSelected(ZenRuleInfo ruleInfo, Fragment parent);
}
@Override
public int getMetricsCategory() {
return SettingsEnums.NOTIFICATION_ZEN_MODE_RULE_SELECTION_DIALOG;
}
public static void show(Context context, Fragment parent, PositiveClickListener
listener, ZenServiceListing serviceListing) {
mPositiveClickListener = listener;
mContext = context;
mPm = mContext.getPackageManager();
mNm = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
mServiceListing = serviceListing;
ZenRuleSelectionDialog dialog = new ZenRuleSelectionDialog();
dialog.setTargetFragment(parent, 0);
dialog.show(parent.getFragmentManager(), TAG);
}
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
final View v = LayoutInflater.from(getContext()).inflate(R.layout.zen_rule_type_selection,
null, false);
mRuleContainer = (LinearLayout) v.findViewById(R.id.rule_container);
if (mServiceListing != null) {
bindType(defaultNewEvent());
bindType(defaultNewSchedule());
mServiceListing.addZenCallback(mServiceListingCallback);
mServiceListing.reloadApprovedServices();
}
return new AlertDialog.Builder(getContext())
.setTitle(R.string.zen_mode_choose_rule_type)
.setView(v)
.setNegativeButton(R.string.cancel, null)
.create();
}
@Override
public void onDismiss(DialogInterface dialog) {
super.onDismiss(dialog);
if (mServiceListing != null) {
mServiceListing.removeZenCallback(mServiceListingCallback);
}
}
// Returns whether the rule's configuration activity exists and is valid.
private boolean isRuleActivityValid(final ZenRuleInfo ri) {
Intent intent = new Intent().setComponent(ri.configurationActivity);
List<ResolveInfo> results = mPm.queryIntentActivities(
intent, PackageManager.ResolveInfoFlags.of(0));
return intent.resolveActivity(mPm) != null && results.size() > 0;
}
private void bindType(final ZenRuleInfo ri) {
try {
ApplicationInfo info = mPm.getApplicationInfo(ri.packageName, 0);
final LinearLayout v = (LinearLayout) LayoutInflater.from(mContext).inflate(
R.layout.zen_rule_type, null, false);
ImageView iconView = v.findViewById(R.id.icon);
((TextView) v.findViewById(R.id.title)).setText(ri.title);
if (!ri.isSystem) {
// Omit rule if the externally provided rule activity is not valid.
if (!isRuleActivityValid(ri)) {
Log.w(TAG, "rule configuration activity invalid: " + ri.configurationActivity);
return;
}
LoadIconTask task = new LoadIconTask(iconView);
task.execute(info);
TextView subtitle = (TextView) v.findViewById(R.id.subtitle);
subtitle.setText(info.loadLabel(mPm));
subtitle.setVisibility(View.VISIBLE);
} else {
if (ZenModeConfig.isValidScheduleConditionId(ri.defaultConditionId)) {
iconView.setImageDrawable(mContext.getDrawable(R.drawable.ic_timelapse));
} else if (ZenModeConfig.isValidEventConditionId(ri.defaultConditionId)) {
iconView.setImageDrawable(mContext.getDrawable(R.drawable.ic_event));
}
}
v.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
dismiss();
if (ri.isSystem) {
mPositiveClickListener.onSystemRuleSelected(ri, getTargetFragment());
} else {
mPositiveClickListener.onExternalRuleSelected(ri, getTargetFragment());
}
}
});
mRuleContainer.addView(v);
} catch (PackageManager.NameNotFoundException e) {
// Omit rule.
}
}
private ZenRuleInfo defaultNewSchedule() {
final ZenModeConfig.ScheduleInfo schedule = new ZenModeConfig.ScheduleInfo();
schedule.days = ZenModeConfig.ALL_DAYS;
schedule.startHour = 22;
schedule.endHour = 7;
final ZenRuleInfo rt = new ZenRuleInfo();
rt.settingsAction = ZenModeScheduleRuleSettings.ACTION;
rt.title = mContext.getString(R.string.zen_schedule_rule_type_name);
rt.packageName = ZenModeConfig.getEventConditionProvider().getPackageName();
rt.defaultConditionId = ZenModeConfig.toScheduleConditionId(schedule);
rt.serviceComponent = ZenModeConfig.getScheduleConditionProvider();
rt.isSystem = true;
return rt;
}
private ZenRuleInfo defaultNewEvent() {
final ZenModeConfig.EventInfo event = new ZenModeConfig.EventInfo();
event.calName = null; // any calendar
event.calendarId = null;
event.reply = ZenModeConfig.EventInfo.REPLY_ANY_EXCEPT_NO;
final ZenRuleInfo rt = new ZenRuleInfo();
rt.settingsAction = ZenModeEventRuleSettings.ACTION;
rt.title = mContext.getString(R.string.zen_event_rule_type_name);
rt.packageName = ZenModeConfig.getScheduleConditionProvider().getPackageName();
rt.defaultConditionId = ZenModeConfig.toEventConditionId(event);
rt.serviceComponent = ZenModeConfig.getEventConditionProvider();
rt.isSystem = true;
return rt;
}
private void bindExternalRules(Set<ZenRuleInfo> externalRuleTypes) {
for (ZenRuleInfo ri : externalRuleTypes) {
bindType(ri);
}
}
private final ZenServiceListing.Callback mServiceListingCallback = new
ZenServiceListing.Callback() {
@Override
public void onComponentsReloaded(Set<ComponentInfo> componentInfos) {
if (DEBUG) Log.d(TAG, "Reloaded: count=" + componentInfos.size());
Set<ZenRuleInfo> externalRuleTypes = new TreeSet<>(RULE_TYPE_COMPARATOR);
for (ComponentInfo ci : componentInfos) {
final ZenRuleInfo ri = AbstractZenModeAutomaticRulePreferenceController.
getRuleInfo(mPm, ci);
if (ri != null && ri.configurationActivity != null
&& mNm.isNotificationPolicyAccessGrantedForPackage(ri.packageName)
&& (ri.ruleInstanceLimit <= 0 || ri.ruleInstanceLimit
>= (mNm.getRuleInstanceCount(ci.getComponentName()) + 1))) {
externalRuleTypes.add(ri);
}
}
bindExternalRules(externalRuleTypes);
}
};
private static final Comparator<ZenRuleInfo> RULE_TYPE_COMPARATOR =
new Comparator<ZenRuleInfo>() {
private final Collator mCollator = Collator.getInstance();
@Override
public int compare(ZenRuleInfo lhs, ZenRuleInfo rhs) {
int byAppName = mCollator.compare(lhs.packageLabel, rhs.packageLabel);
if (byAppName != 0) {
return byAppName;
} else {
return mCollator.compare(lhs.title, rhs.title);
}
}
};
private class LoadIconTask extends AsyncTask<ApplicationInfo, Void, Drawable> {
private final WeakReference<ImageView> viewReference;
public LoadIconTask(ImageView view) {
viewReference = new WeakReference<>(view);
}
@Override
protected Drawable doInBackground(ApplicationInfo... params) {
return params[0].loadIcon(mPm);
}
@Override
protected void onPostExecute(Drawable icon) {
if (icon != null) {
final ImageView view = viewReference.get();
if (view != null) {
view.setImageDrawable(icon);
}
}
}
}
}