Merge changes from topics "add-mode-dialog", "nicer-icon-header" into main

* changes:
  Icon picker: Styling improvements
  Add mode: Support for app-provided modes
This commit is contained in:
Matías Hernández
2024-07-02 12:26:42 +00:00
committed by Android (Google) Code Review
40 changed files with 1435 additions and 374 deletions

View File

@@ -16,6 +16,8 @@
package com.android.settings.notification.modes;
import static com.google.common.base.Preconditions.checkState;
import android.app.Flags;
import android.content.Context;
import android.service.notification.ZenPolicy;
@@ -30,8 +32,6 @@ import com.android.settingslib.core.AbstractPreferenceController;
import com.android.settingslib.notification.modes.ZenMode;
import com.android.settingslib.notification.modes.ZenModesBackend;
import com.google.common.base.Preconditions;
import java.util.function.Function;
/**
@@ -41,8 +41,8 @@ abstract class AbstractZenModePreferenceController extends AbstractPreferenceCon
private static final String TAG = "AbstractZenModePreferenceController";
@Nullable
protected ZenModesBackend mBackend;
@Nullable protected final ZenModesBackend mBackend;
@Nullable // only until setZenMode() is called
private ZenMode mZenMode;
@@ -50,14 +50,27 @@ abstract class AbstractZenModePreferenceController extends AbstractPreferenceCon
@NonNull
private final String mKey;
// ZenModesBackend should only be passed in if the preference controller may set the user's
// policy for this zen mode. Otherwise, if the preference controller is essentially read-only
// and leads to a further Settings screen, backend should be null.
AbstractZenModePreferenceController(@NonNull Context context, @NonNull String key,
@Nullable ZenModesBackend backend) {
/**
* Constructor suitable for "read-only" controllers (e.g. link to a different sub-screen.
* Controllers that call this constructor to initialize themselves <em>cannot</em> call
* {@link #saveMode} or {@link #savePolicy} later.
*/
AbstractZenModePreferenceController(@NonNull Context context, @NonNull String key) {
super(context);
mBackend = backend;
mKey = key;
mBackend = null;
}
/**
* Constructor suitable for controllers that will update the associated {@link ZenMode}.
* Controllers that call this constructor to initialize themselves may call {@link #saveMode} or
* {@link #savePolicy} later.
*/
AbstractZenModePreferenceController(@NonNull Context context, @NonNull String key,
@NonNull ZenModesBackend backend) {
super(context);
mKey = key;
mBackend = backend;
}
@Override
@@ -135,7 +148,7 @@ abstract class AbstractZenModePreferenceController extends AbstractPreferenceCon
* instance is ok.
*/
protected final boolean saveMode(Function<ZenMode, ZenMode> updater) {
Preconditions.checkState(mBackend != null);
checkState(mBackend != null);
ZenMode mode = mZenMode;
if (mode == null) {
Log.wtf(TAG, "Cannot save mode, it hasn't been loaded (" + getClass() + ")");

View File

@@ -0,0 +1,143 @@
/*
* Copyright (C) 2024 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.modes;
import static android.app.NotificationManager.EXTRA_AUTOMATIC_RULE_ID;
import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.ComponentInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.service.notification.ConditionProviderService;
import android.util.Log;
import androidx.annotation.Nullable;
import com.android.settingslib.notification.modes.ZenMode;
import java.util.List;
import java.util.function.Function;
class ConfigurationActivityHelper {
private static final String TAG = "ConfigurationActivityHelper";
private final PackageManager mPm;
ConfigurationActivityHelper(PackageManager pm) {
mPm = pm;
}
@Nullable
Intent getConfigurationActivityIntentForMode(ZenMode zenMode,
Function<ComponentName, ComponentInfo> approvedServiceFinder) {
String owner = zenMode.getRule().getPackageName();
ComponentName configActivity = null;
if (zenMode.getRule().getConfigurationActivity() != null) {
// If a configuration activity is present, use that directly in the intent
configActivity = zenMode.getRule().getConfigurationActivity();
} else {
// Otherwise, look for a condition provider service for the rule's package
ComponentInfo ci = approvedServiceFinder.apply(zenMode.getRule().getOwner());
if (ci != null) {
configActivity = extractConfigurationActivityFromComponent(ci);
}
}
if (configActivity != null
&& (owner == null || isSameOwnerPackage(owner, configActivity))
&& isResolvableActivity(configActivity)) {
return new Intent()
.setComponent(configActivity)
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
.putExtra(ConditionProviderService.EXTRA_RULE_ID, zenMode.getId())
.putExtra(EXTRA_AUTOMATIC_RULE_ID, zenMode.getId());
} else {
return null;
}
}
@Nullable
ComponentName getConfigurationActivityFromApprovedComponent(ComponentInfo ci) {
ComponentName configActivity = extractConfigurationActivityFromComponent(ci);
if (configActivity != null
&& isSameOwnerPackage(ci.packageName, configActivity)
&& isResolvableActivity(configActivity)) {
return configActivity;
} else {
return null;
}
}
/**
* Extract the {@link ComponentName} corresponding to the mode configuration <em>activity</em>
* from the component declaring the rule (which may be the Activity itself, or a CPS that points
* to the activity in question in its metadata).
*
* <p>This method doesn't perform any validation, so the activity may or may not exist.
*/
@Nullable
private ComponentName extractConfigurationActivityFromComponent(ComponentInfo ci) {
if (ci instanceof ActivityInfo) {
// New (activity-backed) rule.
return new ComponentName(ci.packageName, ci.name);
} else if (ci.metaData != null) {
// Old (service-backed) rule.
final String configurationActivity = ci.metaData.getString(
ConditionProviderService.META_DATA_CONFIGURATION_ACTIVITY);
if (configurationActivity != null) {
return ComponentName.unflattenFromString(configurationActivity);
}
}
return null;
}
/**
* Verifies that the activity is the same package as the rule owner.
*/
private boolean isSameOwnerPackage(String ownerPkg, ComponentName activityName) {
try {
int ownerUid = mPm.getPackageUid(ownerPkg, 0);
int configActivityOwnerUid = mPm.getPackageUid(activityName.getPackageName(), 0);
if (ownerUid == configActivityOwnerUid) {
return true;
} else {
Log.w(TAG, String.format("Config activity (%s) not in owner package (%s)",
activityName, ownerPkg));
return false;
}
} catch (PackageManager.NameNotFoundException e) {
Log.e(TAG, "Failed to find config activity " + activityName);
return false;
}
}
/** Verifies that the activity exists and hasn't been disabled. */
private boolean isResolvableActivity(ComponentName activityName) {
Intent intent = new Intent().setComponent(activityName);
List<ResolveInfo> results = mPm.queryIntentActivities(intent, /* flags= */ 0);
if (intent.resolveActivity(mPm) == null || results.isEmpty()) {
Log.w(TAG, "Cannot resolve: " + activityName);
return false;
}
return true;
}
}

View File

@@ -19,14 +19,17 @@ package com.android.settings.notification.modes;
import static com.google.common.base.Preconditions.checkNotNull;
import android.content.Context;
import android.content.res.ColorStateList;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable;
import android.graphics.drawable.ShapeDrawable;
import android.graphics.drawable.shapes.OvalShape;
import android.view.Gravity;
import androidx.annotation.AttrRes;
import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull;
import androidx.annotation.Px;
import com.android.settings.R;
import com.android.settingslib.Utils;
@@ -49,32 +52,52 @@ class IconUtil {
}
/**
* Returns a variant of the supplied {@code icon} to be used in the icon picker. The inner icon
* is 36x36dp and it's contained into a circle of diameter 54dp. It's also set up so that
* selection and pressed states are represented in the color.
* Returns a variant of the supplied {@code icon} to be used as the header in the icon picker.
* The inner icon is 48x48dp and it's contained into a circle of diameter 90dp.
*/
static Drawable makeIconCircle(@NonNull Context context, @NonNull Drawable icon) {
static Drawable makeBigIconCircle(@NonNull Context context, Drawable icon) {
return composeIconCircle(
Utils.getColorAttr(context,
com.android.internal.R.attr.materialColorSecondaryContainer),
context.getResources().getDimensionPixelSize(
R.dimen.zen_mode_icon_list_header_circle_diameter),
icon,
Utils.getColorAttr(context,
com.android.internal.R.attr.materialColorOnSecondaryContainer),
context.getResources().getDimensionPixelSize(
R.dimen.zen_mode_icon_list_header_icon_size));
}
/**
* Returns a variant of the supplied {@code icon} to be used as an option in the icon picker.
* The inner icon is 36x36dp and it's contained into a circle of diameter 54dp. It's also set up
* so that selection and pressed states are represented in the color.
*/
static Drawable makeSmallIconCircle(@NonNull Context context, @DrawableRes int iconResId) {
return composeIconCircle(
context.getColorStateList(R.color.modes_icon_picker_item_background),
context.getResources().getDimensionPixelSize(
R.dimen.zen_mode_icon_list_item_circle_diameter),
checkNotNull(context.getDrawable(iconResId)),
context.getColorStateList(R.color.modes_icon_picker_item_icon),
context.getResources().getDimensionPixelSize(
R.dimen.zen_mode_icon_list_item_icon_size));
}
private static Drawable composeIconCircle(ColorStateList circleColor, @Px int circleDiameterPx,
Drawable icon, ColorStateList iconColor, @Px int iconSizePx) {
ShapeDrawable background = new ShapeDrawable(new OvalShape());
background.setTintList(
context.getColorStateList(R.color.modes_icon_picker_item_background));
icon = icon.mutate();
icon.setTintList(
context.getColorStateList(R.color.modes_icon_picker_item_icon));
background.setTintList(circleColor);
Drawable foreground = checkNotNull(icon.getConstantState()).newDrawable().mutate();
foreground.setTintList(iconColor);
LayerDrawable layerDrawable = new LayerDrawable(new Drawable[] { background, icon });
LayerDrawable layerDrawable = new LayerDrawable(new Drawable[] { background, foreground });
int circleDiameter = context.getResources().getDimensionPixelSize(
R.dimen.zen_mode_icon_list_circle_diameter);
int iconSize = context.getResources().getDimensionPixelSize(
R.dimen.zen_mode_icon_list_icon_size);
int iconPadding = (circleDiameter - iconSize) / 2;
layerDrawable.setBounds(0, 0, circleDiameter, circleDiameter);
layerDrawable.setLayerInset(1, iconPadding, iconPadding, iconPadding, iconPadding);
layerDrawable.setBounds(0, 0, circleDiameterPx, circleDiameterPx);
layerDrawable.setLayerSize(0, circleDiameterPx, circleDiameterPx);
layerDrawable.setLayerGravity(1, Gravity.CENTER);
layerDrawable.setLayerSize(1, iconSizePx, iconSizePx);
return layerDrawable;
}
static Drawable makeIconCircle(@NonNull Context context, @DrawableRes int iconResId) {
return makeIconCircle(context, checkNotNull(context.getDrawable(iconResId)));
}
}

View File

@@ -22,20 +22,17 @@ import android.content.Context;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.preference.Preference;
import com.android.settings.R;
import com.android.settings.core.SubSettingLauncher;
import com.android.settingslib.notification.modes.ZenMode;
import com.android.settingslib.notification.modes.ZenModesBackend;
import com.android.settingslib.widget.ActionButtonsPreference;
class ZenModeActionsPreferenceController extends AbstractZenModePreferenceController {
ZenModeActionsPreferenceController(@NonNull Context context, @NonNull String key,
@Nullable ZenModesBackend backend) {
super(context, key, backend);
ZenModeActionsPreferenceController(@NonNull Context context, @NonNull String key) {
super(context, key);
}
@Override

View File

@@ -24,7 +24,6 @@ import android.os.Bundle;
import android.service.notification.ZenPolicy;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
@@ -44,7 +43,7 @@ public class ZenModeAppsPreferenceController extends
String mModeId;
public ZenModeAppsPreferenceController(@NonNull Context context,
@NonNull String key, @Nullable ZenModesBackend backend) {
@NonNull String key, @NonNull ZenModesBackend backend) {
super(context, key, backend);
}

View File

@@ -26,15 +26,14 @@ import androidx.preference.Preference;
import com.android.settings.core.SubSettingLauncher;
import com.android.settingslib.notification.modes.ZenMode;
import com.android.settingslib.notification.modes.ZenModesBackend;
class ZenModeCallsLinkPreferenceController extends AbstractZenModePreferenceController {
private final ZenModeSummaryHelper mSummaryHelper;
public ZenModeCallsLinkPreferenceController(Context context, String key,
ZenModesBackend backend, ZenHelperBackend helperBackend) {
super(context, key, backend);
ZenHelperBackend helperBackend) {
super(context, key);
mSummaryHelper = new ZenModeSummaryHelper(context, helperBackend);
}

View File

@@ -35,7 +35,7 @@ public class ZenModeDisplayFragment extends ZenModeFragmentBase {
protected List<AbstractPreferenceController> createPreferenceControllers(Context context) {
List<AbstractPreferenceController> prefControllers = new ArrayList<>();
prefControllers.add(new ZenModeNotifVisLinkPreferenceController(
context, "notification_visibility", mBackend, mHelperBackend));
context, "notification_visibility", mHelperBackend));
prefControllers.add(new ZenModeDisplayEffectPreferenceController(
context, "effect_greyscale", mBackend));
prefControllers.add(new ZenModeDisplayEffectPreferenceController(

View File

@@ -47,23 +47,22 @@ public class ZenModeFragment extends ZenModeFragmentBase {
@Override
protected List<AbstractPreferenceController> createPreferenceControllers(Context context) {
List<AbstractPreferenceController> prefControllers = new ArrayList<>();
prefControllers.add(new ZenModeHeaderController(context, "header", this, mBackend));
prefControllers.add(new ZenModeHeaderController(context, "header", this));
prefControllers.add(
new ZenModeButtonPreferenceController(context, "activate", this, mBackend));
prefControllers.add(new ZenModeActionsPreferenceController(context, "actions", mBackend));
prefControllers.add(new ZenModeActionsPreferenceController(context, "actions"));
prefControllers.add(new ZenModePeopleLinkPreferenceController(
context, "zen_mode_people", mBackend, mHelperBackend));
context, "zen_mode_people", mHelperBackend));
prefControllers.add(new ZenModeAppsLinkPreferenceController(
context, "zen_mode_apps", this,
ApplicationsState.getInstance((Application) context.getApplicationContext()),
mBackend, mHelperBackend));
prefControllers.add(new ZenModeOtherLinkPreferenceController(
context, "zen_other_settings", mBackend, mHelperBackend));
context, "zen_other_settings", mHelperBackend));
prefControllers.add(new ZenModeDisplayLinkPreferenceController(
context, "mode_display_settings", mBackend, mHelperBackend));
prefControllers.add(new ZenModeSetTriggerLinkPreferenceController(context,
"zen_automatic_trigger_category", this, mBackend,
context.getPackageManager()));
"zen_automatic_trigger_category", this, mBackend));
prefControllers.add(new InterruptionFilterPreferenceController(
context, "allow_filtering", mBackend));
prefControllers.add(new ManualDurationPreferenceController(

View File

@@ -19,7 +19,6 @@ import android.app.Flags;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.preference.Preference;
import com.android.settings.R;
@@ -27,7 +26,6 @@ import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.widget.EntityHeaderController;
import com.android.settingslib.notification.modes.ZenIconLoader;
import com.android.settingslib.notification.modes.ZenMode;
import com.android.settingslib.notification.modes.ZenModesBackend;
import com.android.settingslib.widget.LayoutPreference;
class ZenModeHeaderController extends AbstractZenModePreferenceController {
@@ -38,9 +36,8 @@ class ZenModeHeaderController extends AbstractZenModePreferenceController {
ZenModeHeaderController(
@NonNull Context context,
@NonNull String key,
@NonNull DashboardFragment fragment,
@Nullable ZenModesBackend backend) {
super(context, key, backend);
@NonNull DashboardFragment fragment) {
super(context, key);
mFragment = fragment;
}

View File

@@ -41,10 +41,9 @@ public class ZenModeIconPickerFragment extends ZenModeFragmentBase {
@Override
protected List<AbstractPreferenceController> createPreferenceControllers(Context context) {
return ImmutableList.of(
new ZenModeIconPickerIconPreferenceController(context, "current_icon", this,
mBackend),
new ZenModeIconPickerIconPreferenceController(context, "current_icon", this),
new ZenModeIconPickerListPreferenceController(context, "icon_list",
mIconPickerListener, new IconOptionsProviderImpl(mContext), mBackend));
mIconPickerListener));
}
private final ZenModeIconPickerListPreferenceController.IconPickerListener mIconPickerListener =

View File

@@ -17,9 +17,10 @@
package com.android.settings.notification.modes;
import android.content.Context;
import android.view.ViewGroup;
import android.widget.ImageView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.preference.Preference;
import com.android.settings.R;
@@ -27,17 +28,17 @@ import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.widget.EntityHeaderController;
import com.android.settingslib.notification.modes.ZenIconLoader;
import com.android.settingslib.notification.modes.ZenMode;
import com.android.settingslib.notification.modes.ZenModesBackend;
import com.android.settingslib.widget.LayoutPreference;
/** Controller used for displaying the currently-chosen icon at the top of the icon picker. */
class ZenModeIconPickerIconPreferenceController extends AbstractZenModePreferenceController {
private final DashboardFragment mFragment;
private EntityHeaderController mHeaderController;
ZenModeIconPickerIconPreferenceController(@NonNull Context context, @NonNull String key,
@NonNull DashboardFragment fragment, @Nullable ZenModesBackend backend) {
super(context, key, backend);
@NonNull DashboardFragment fragment) {
super(context, key);
mFragment = fragment;
}
@@ -51,11 +52,19 @@ class ZenModeIconPickerIconPreferenceController extends AbstractZenModePreferenc
mFragment.getActivity(),
mFragment,
pref.findViewById(R.id.entity_header));
ImageView iconView = pref.findViewById(R.id.entity_header_icon);
ViewGroup.LayoutParams layoutParams = iconView.getLayoutParams();
int imageSizePx = iconView.getContext().getResources().getDimensionPixelSize(
R.dimen.zen_mode_icon_list_header_circle_diameter);
layoutParams.width = imageSizePx;
layoutParams.height = imageSizePx;
iconView.setLayoutParams(layoutParams);
}
FutureUtil.whenDone(
zenMode.getIcon(mContext, ZenIconLoader.getInstance()),
icon -> mHeaderController.setIcon(IconUtil.applyNormalTint(mContext, icon))
icon -> mHeaderController.setIcon(IconUtil.makeBigIconCircle(mContext, icon))
.done(/* rebindActions= */ false),
mContext.getMainExecutor());
}

View File

@@ -26,6 +26,7 @@ import android.widget.ImageView;
import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
import androidx.recyclerview.widget.GridLayoutManager;
@@ -33,8 +34,8 @@ import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.SimpleItemAnimator;
import com.android.settings.R;
import com.android.settingslib.notification.modes.ZenIconLoader;
import com.android.settingslib.notification.modes.ZenMode;
import com.android.settingslib.notification.modes.ZenModesBackend;
import com.android.settingslib.widget.LayoutPreference;
import com.google.common.collect.ImmutableList;
@@ -51,9 +52,15 @@ class ZenModeIconPickerListPreferenceController extends AbstractZenModePreferenc
private @DrawableRes int mCurrentIconResId;
ZenModeIconPickerListPreferenceController(@NonNull Context context, @NonNull String key,
@NonNull IconPickerListener listener, @NonNull IconOptionsProvider iconOptionsProvider,
@Nullable ZenModesBackend backend) {
super(context, key, backend);
@NonNull IconPickerListener listener) {
this(context, key, listener, new IconOptionsProviderImpl(context));
}
@VisibleForTesting
ZenModeIconPickerListPreferenceController(@NonNull Context context, @NonNull String key,
@NonNull IconPickerListener listener,
@NonNull IconOptionsProvider iconOptionsProvider) {
super(context, key);
mListener = listener;
mIconOptionsProvider = iconOptionsProvider;
}
@@ -80,7 +87,11 @@ class ZenModeIconPickerListPreferenceController extends AbstractZenModePreferenc
@Override
void updateState(Preference preference, @NonNull ZenMode zenMode) {
updateIconSelection(zenMode.getRule().getIconResId());
@DrawableRes int iconResId = zenMode.getRule().getIconResId();
if (iconResId == 0) {
iconResId = ZenIconLoader.getIconResourceIdFromType(zenMode.getType());
}
updateIconSelection(iconResId);
}
private void updateIconSelection(@DrawableRes int iconResId) {
@@ -145,7 +156,7 @@ class ZenModeIconPickerListPreferenceController extends AbstractZenModePreferenc
public void onBindViewHolder(@NonNull IconHolder holder, int position) {
IconOptionsProvider.IconInfo iconInfo = mIconResources.get(position);
Drawable iconDrawable = mIconCache.computeIfAbsent(iconInfo,
info -> IconUtil.makeIconCircle(mContext, info.resId()));
info -> IconUtil.makeSmallIconCircle(mContext, info.resId()));
holder.bindIcon(iconInfo, iconDrawable);
}

View File

@@ -26,14 +26,13 @@ import androidx.preference.Preference;
import com.android.settings.core.SubSettingLauncher;
import com.android.settingslib.notification.modes.ZenMode;
import com.android.settingslib.notification.modes.ZenModesBackend;
class ZenModeMessagesLinkPreferenceController extends AbstractZenModePreferenceController {
private final ZenModeSummaryHelper mSummaryHelper;
public ZenModeMessagesLinkPreferenceController(Context context, String key,
ZenModesBackend backend, ZenHelperBackend helperBackend) {
super(context, key, backend);
ZenHelperBackend helperBackend) {
super(context, key);
mSummaryHelper = new ZenModeSummaryHelper(context, helperBackend);
}

View File

@@ -27,15 +27,14 @@ import androidx.preference.Preference;
import com.android.settings.core.SubSettingLauncher;
import com.android.settingslib.notification.modes.ZenMode;
import com.android.settingslib.notification.modes.ZenModesBackend;
class ZenModeNotifVisLinkPreferenceController extends AbstractZenModePreferenceController {
private final ZenModeSummaryHelper mSummaryBuilder;
public ZenModeNotifVisLinkPreferenceController(Context context, String key,
ZenModesBackend backend, ZenHelperBackend helperBackend) {
super(context, key, backend);
ZenHelperBackend helperBackend) {
super(context, key);
mSummaryBuilder = new ZenModeSummaryHelper(context, helperBackend);
}

View File

@@ -27,7 +27,6 @@ import androidx.preference.Preference;
import com.android.settings.core.SubSettingLauncher;
import com.android.settingslib.notification.modes.ZenMode;
import com.android.settingslib.notification.modes.ZenModesBackend;
/**
* Preference with a link and summary about what other sounds can break through the mode
@@ -37,8 +36,8 @@ class ZenModeOtherLinkPreferenceController extends AbstractZenModePreferenceCont
private final ZenModeSummaryHelper mSummaryHelper;
public ZenModeOtherLinkPreferenceController(Context context, String key,
ZenModesBackend backend, ZenHelperBackend helperBackend) {
super(context, key, backend);
ZenHelperBackend helperBackend) {
super(context, key);
mSummaryHelper = new ZenModeSummaryHelper(mContext, helperBackend);
}

View File

@@ -35,9 +35,9 @@ public class ZenModePeopleFragment extends ZenModeFragmentBase {
protected List<AbstractPreferenceController> createPreferenceControllers(Context context) {
List<AbstractPreferenceController> prefControllers = new ArrayList<>();
prefControllers.add(new ZenModeCallsLinkPreferenceController(
context, "zen_mode_people_calls", mBackend, mHelperBackend));
context, "zen_mode_people_calls", mHelperBackend));
prefControllers.add(new ZenModeMessagesLinkPreferenceController(
context, "zen_mode_people_messages", mBackend, mHelperBackend));
context, "zen_mode_people_messages", mHelperBackend));
return prefControllers;
}

View File

@@ -27,7 +27,6 @@ import androidx.preference.Preference;
import com.android.settings.core.SubSettingLauncher;
import com.android.settingslib.notification.modes.ZenMode;
import com.android.settingslib.notification.modes.ZenModesBackend;
/**
* Preference with a link and summary about what calls and messages can break through the mode
@@ -37,8 +36,8 @@ class ZenModePeopleLinkPreferenceController extends AbstractZenModePreferenceCon
private final ZenModeSummaryHelper mSummaryHelper;
public ZenModePeopleLinkPreferenceController(Context context, String key,
ZenModesBackend backend, ZenHelperBackend helperBackend) {
super(context, key, backend);
ZenHelperBackend helperBackend) {
super(context, key);
mSummaryHelper = new ZenModeSummaryHelper(mContext, helperBackend);
}

View File

@@ -18,20 +18,12 @@ package com.android.settings.notification.modes;
import static android.app.AutomaticZenRule.TYPE_SCHEDULE_CALENDAR;
import static android.app.AutomaticZenRule.TYPE_SCHEDULE_TIME;
import static android.app.NotificationManager.EXTRA_AUTOMATIC_RULE_ID;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.ComponentInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.service.notification.ConditionProviderService;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.preference.Preference;
import androidx.preference.PreferenceCategory;
@@ -39,14 +31,10 @@ import androidx.preference.PreferenceScreen;
import com.android.settings.R;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.utils.ManagedServiceSettings;
import com.android.settings.utils.ZenServiceListing;
import com.android.settingslib.PrimarySwitchPreference;
import com.android.settingslib.notification.modes.ZenMode;
import com.android.settingslib.notification.modes.ZenModesBackend;
import java.util.List;
/**
* Preference controller for the link to an individual mode's configuration page.
*/
@@ -56,23 +44,25 @@ class ZenModeSetTriggerLinkPreferenceController extends AbstractZenModePreferenc
@VisibleForTesting
protected static final String AUTOMATIC_TRIGGER_PREF_KEY = "zen_automatic_trigger_settings";
private static final ManagedServiceSettings.Config CONFIG =
ZenModesListFragment.getConditionProviderConfig();
private ZenServiceListing mServiceListing;
private final PackageManager mPm;
private final ConfigurationActivityHelper mConfigurationActivityHelper;
private final ZenServiceListing mServiceListing;
private final DashboardFragment mFragment;
ZenModeSetTriggerLinkPreferenceController(Context context, String key,
DashboardFragment fragment, ZenModesBackend backend,
PackageManager packageManager) {
super(context, key, backend);
mFragment = fragment;
mPm = packageManager;
DashboardFragment fragment, ZenModesBackend backend) {
this(context, key, fragment, backend,
new ConfigurationActivityHelper(context.getPackageManager()),
new ZenServiceListing(context));
}
@VisibleForTesting
protected void setServiceListing(ZenServiceListing serviceListing) {
ZenModeSetTriggerLinkPreferenceController(Context context, String key,
DashboardFragment fragment, ZenModesBackend backend,
ConfigurationActivityHelper configurationActivityHelper,
ZenServiceListing serviceListing) {
super(context, key, backend);
mFragment = fragment;
mConfigurationActivityHelper = configurationActivityHelper;
mServiceListing = serviceListing;
}
@@ -83,11 +73,9 @@ class ZenModeSetTriggerLinkPreferenceController extends AbstractZenModePreferenc
@Override
public void displayPreference(PreferenceScreen screen, @NonNull ZenMode zenMode) {
if (mServiceListing == null) {
mServiceListing = new ZenServiceListing(
mContext, CONFIG, zenMode.getRule().getPackageName());
}
mServiceListing.reloadApprovedServices();
// Preload approved components, but only for the package that owns the rule (since it's the
// only package that can have a valid configurationActivity).
mServiceListing.loadApprovedComponents(zenMode.getRule().getPackageName());
}
@Override
@@ -130,8 +118,9 @@ class ZenModeSetTriggerLinkPreferenceController extends AbstractZenModePreferenc
});
}
} else {
Intent intent = getAppRuleIntent(zenMode);
if (intent != null && isValidIntent(intent)) {
Intent intent = mConfigurationActivityHelper.getConfigurationActivityIntentForMode(
zenMode, mServiceListing::findService);
if (intent != null) {
preference.setVisible(true);
switchPref.setTitle(R.string.zen_mode_configuration_link_title);
switchPref.setSummary(zenMode.getRule().getTriggerDescription());
@@ -161,68 +150,4 @@ class ZenModeSetTriggerLinkPreferenceController extends AbstractZenModePreferenc
});
// TODO: b/342156843 - Do we want to jump to the corresponding schedule editing screen?
};
@VisibleForTesting
protected @Nullable Intent getAppRuleIntent(ZenMode zenMode) {
Intent intent = new Intent().addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
.putExtra(ConditionProviderService.EXTRA_RULE_ID, zenMode.getId())
.putExtra(EXTRA_AUTOMATIC_RULE_ID, zenMode.getId());
String owner = zenMode.getRule().getPackageName();
ComponentName configActivity = null;
if (zenMode.getRule().getConfigurationActivity() != null) {
// If a configuration activity is present, use that directly in the intent
configActivity = zenMode.getRule().getConfigurationActivity();
} else {
// Otherwise, look for a condition provider service for the rule's package
ComponentInfo ci = mServiceListing.findService(zenMode.getRule().getOwner());
if (ci == null) {
// do nothing
} else if (ci instanceof ActivityInfo) {
// new activity backed rule
intent.setComponent(new ComponentName(ci.packageName, ci.name));
return intent;
} else if (ci.metaData != null) {
// old service backed rule
final String configurationActivity = ci.metaData.getString(
ConditionProviderService.META_DATA_CONFIGURATION_ACTIVITY);
if (configurationActivity != null) {
configActivity = ComponentName.unflattenFromString(configurationActivity);
}
}
}
if (configActivity != null) {
// verify that the owner of the rule owns the configuration activity, but only if
// owner exists
intent.setComponent(configActivity);
if (owner == null) {
return intent;
}
try {
int ownerUid = mPm.getPackageUid(owner, 0);
int configActivityOwnerUid = mPm.getPackageUid(configActivity.getPackageName(), 0);
if (ownerUid == configActivityOwnerUid) {
return intent;
} else {
Log.w(TAG, "Config activity not in owner package for "
+ zenMode.getRule().getName());
return null;
}
} catch (PackageManager.NameNotFoundException e) {
Log.e(TAG, "Failed to find config activity");
return null;
}
}
return null;
}
private boolean isValidIntent(Intent intent) {
List<ResolveInfo> results = mPm.queryIntentActivities(
intent, PackageManager.ResolveInfoFlags.of(0));
if (intent.resolveActivity(mPm) == null || results.size() == 0) {
Log.w(TAG, "intent for zen rule invalid: " + intent);
return false;
}
return true;
}
}

View File

@@ -26,6 +26,8 @@ import android.os.UserManager;
import android.provider.Settings.Global;
import android.util.Log;
import androidx.annotation.VisibleForTesting;
import com.android.settings.dashboard.RestrictedDashboardFragment;
import com.android.settingslib.notification.modes.ZenModesBackend;
@@ -57,6 +59,11 @@ abstract class ZenModesFragmentBase extends RestrictedDashboardFragment {
return TAG;
}
@VisibleForTesting(otherwise = VisibleForTesting.NONE)
void setBackend(ZenModesBackend backend) {
mBackend = backend;
}
@Override
public void onAttach(@NonNull Context context) {
mContext = context;

View File

@@ -16,27 +16,82 @@
package com.android.settings.notification.modes;
import android.app.NotificationManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.ComponentInfo;
import android.content.pm.PackageManager;
import android.content.pm.ServiceInfo;
import android.graphics.drawable.Drawable;
import android.service.notification.ConditionProviderService;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.annotation.WorkerThread;
import androidx.preference.Preference;
import com.android.settings.utils.ZenServiceListing;
import com.android.settings.R;
import com.android.settingslib.Utils;
import com.android.settingslib.core.AbstractPreferenceController;
import com.android.settingslib.notification.modes.ZenMode;
import com.android.settingslib.notification.modes.ZenModesBackend;
import java.util.Random;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.MoreExecutors;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.function.Function;
class ZenModesListAddModePreferenceController extends AbstractPreferenceController {
private final ZenModesBackend mBackend;
private final ZenServiceListing mServiceListing;
private final OnAddModeListener mOnAddModeListener;
ZenModesListAddModePreferenceController(Context context, ZenModesBackend backend,
ZenServiceListing serviceListing) {
private final ConfigurationActivityHelper mConfigurationActivityHelper;
private final NotificationManager mNotificationManager;
private final PackageManager mPackageManager;
private final Function<ApplicationInfo, Drawable> mAppIconRetriever;
private final ListeningExecutorService mBackgroundExecutor;
private final Executor mUiThreadExecutor;
record ModeType(String name, Drawable icon, @Nullable String summary,
@Nullable Intent creationActivityIntent) { }
interface OnAddModeListener {
void onAvailableModeTypesForAdd(List<ModeType> types);
}
ZenModesListAddModePreferenceController(Context context, OnAddModeListener onAddModeListener) {
this(context, onAddModeListener, new ZenServiceListing(context),
new ConfigurationActivityHelper(context.getPackageManager()),
context.getSystemService(NotificationManager.class), context.getPackageManager(),
applicationInfo -> Utils.getBadgedIcon(context, applicationInfo),
Executors.newCachedThreadPool(), context.getMainExecutor());
}
@VisibleForTesting
ZenModesListAddModePreferenceController(Context context,
OnAddModeListener onAddModeListener, ZenServiceListing serviceListing,
ConfigurationActivityHelper configurationActivityHelper,
NotificationManager notificationManager, PackageManager packageManager,
Function<ApplicationInfo, Drawable> appIconRetriever,
ExecutorService backgroundExecutor, Executor uiThreadExecutor) {
super(context);
mBackend = backend;
mOnAddModeListener = onAddModeListener;
mServiceListing = serviceListing;
mConfigurationActivityHelper = configurationActivityHelper;
mNotificationManager = notificationManager;
mPackageManager = packageManager;
mAppIconRetriever = appIconRetriever;
mBackgroundExecutor = MoreExecutors.listeningDecorator(backgroundExecutor);
mUiThreadExecutor = uiThreadExecutor;
}
@Override
@@ -52,12 +107,79 @@ class ZenModesListAddModePreferenceController extends AbstractPreferenceControll
@Override
public void updateState(Preference preference) {
preference.setOnPreferenceClickListener(pref -> {
// TODO: b/326442408 - Launch the proper mode creation flow (using mServiceListing).
ZenMode mode = mBackend.addCustomMode("New mode #" + new Random().nextInt(1000));
if (mode != null) {
ZenSubSettingLauncher.forMode(mContext, mode.getId()).launch();
}
onClickAddMode();
return true;
});
}
@VisibleForTesting
void onClickAddMode() {
FutureUtil.whenDone(
mBackgroundExecutor.submit(this::getModeProviders),
mOnAddModeListener::onAvailableModeTypesForAdd,
mUiThreadExecutor);
}
@WorkerThread
private ImmutableList<ModeType> getModeProviders() {
ImmutableSet<ComponentInfo> approvedComponents = mServiceListing.loadApprovedComponents();
ArrayList<ModeType> appProvidedModes = new ArrayList<>();
for (ComponentInfo ci: approvedComponents) {
ModeType modeType = getValidNewModeTypeFromComponent(ci);
if (modeType != null) {
appProvidedModes.add(modeType);
}
}
return ImmutableList.<ModeType>builder()
.add(new ModeType(
mContext.getString(R.string.zen_mode_new_option_custom),
mContext.getDrawable(R.drawable.ic_zen_mode_new_option_custom),
null, null))
.addAll(appProvidedModes.stream()
.sorted(Comparator.comparing(ModeType::name))
.toList())
.build();
}
/**
* Returns a {@link ModeType} object corresponding to the approved {@link ComponentInfo} that
* specifies a creatable rule, if such a mode can actually be created (has an associated and
* enabled configuration activity, has not exceeded the rule instance limit, etc). Otherwise,
* returns {@code null}.
*/
@WorkerThread
@Nullable
private ModeType getValidNewModeTypeFromComponent(ComponentInfo ci) {
if (ci.metaData == null) {
return null;
}
String ruleType = (ci instanceof ServiceInfo)
? ci.metaData.getString(ConditionProviderService.META_DATA_RULE_TYPE)
: ci.metaData.getString(NotificationManager.META_DATA_AUTOMATIC_RULE_TYPE);
if (ruleType == null || ruleType.trim().isEmpty()) {
return null;
}
int ruleInstanceLimit = (ci instanceof ServiceInfo)
? ci.metaData.getInt(ConditionProviderService.META_DATA_RULE_INSTANCE_LIMIT, -1)
: ci.metaData.getInt(NotificationManager.META_DATA_RULE_INSTANCE_LIMIT, -1);
if (ruleInstanceLimit > 0 && mNotificationManager.getRuleInstanceCount(
ci.getComponentName()) >= ruleInstanceLimit) {
return null; // Would exceed instance limit.
}
ComponentName configurationActivity =
mConfigurationActivityHelper.getConfigurationActivityFromApprovedComponent(ci);
if (configurationActivity == null) {
return null;
}
String appName = ci.applicationInfo.loadLabel(mPackageManager).toString();
Drawable appIcon = mAppIconRetriever.apply(ci.applicationInfo);
Intent configActivityIntent = new Intent().setComponent(configurationActivity);
return new ModeType(ruleType, appIcon, appName, configActivityIntent);
}
}

View File

@@ -0,0 +1,116 @@
/*
* Copyright (C) 2024 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.modes;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import android.app.Dialog;
import android.content.Context;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import com.android.settings.R;
import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.notification.modes.ZenModesListAddModePreferenceController.ModeType;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
import java.util.List;
public class ZenModesListAddModeTypeChooserDialog extends InstrumentedDialogFragment {
private static final String TAG = "ZenModesListAddModeTypeChooserDialog";
private OnChooseModeTypeListener mChooseModeTypeListener;
private ImmutableList<ModeType> mOptions;
interface OnChooseModeTypeListener {
void onTypeSelected(ModeType type);
}
@Override
public int getMetricsCategory() {
// TODO: b/332937635 - Update metrics category
return 0;
}
static void show(DashboardFragment parent,
OnChooseModeTypeListener onChooseModeTypeListener,
List<ModeType> options) {
ZenModesListAddModeTypeChooserDialog dialog = new ZenModesListAddModeTypeChooserDialog();
dialog.mChooseModeTypeListener = onChooseModeTypeListener;
dialog.mOptions = ImmutableList.copyOf(options);
dialog.setTargetFragment(parent, 0);
dialog.show(parent.getParentFragmentManager(), TAG);
}
@NonNull
@Override
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
checkState(getContext() != null);
return new AlertDialog.Builder(getContext())
.setTitle(R.string.zen_mode_new_title)
.setAdapter(new OptionsAdapter(getContext(), mOptions),
(dialog, which) -> mChooseModeTypeListener.onTypeSelected(
mOptions.get(which)))
.setNegativeButton(R.string.cancel, null)
.create();
}
private static class OptionsAdapter extends ArrayAdapter<ModeType> {
private final LayoutInflater mInflater;
private OptionsAdapter(Context context,
ImmutableList<ModeType> availableModeProviders) {
super(context, R.layout.zen_mode_type_item, availableModeProviders);
mInflater = LayoutInflater.from(context);
}
@NonNull
@Override
public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
if (convertView == null) {
convertView = mInflater.inflate(R.layout.zen_mode_type_item, parent, false);
}
ImageView imageView = checkNotNull(convertView.findViewById(R.id.icon));
TextView title = checkNotNull(convertView.findViewById(R.id.title));
TextView subtitle = checkNotNull(convertView.findViewById(R.id.subtitle));
ModeType option = checkNotNull(getItem(position));
imageView.setImageDrawable(option.icon());
title.setText(option.name());
subtitle.setText(option.summary());
subtitle.setVisibility(
Strings.isNullOrEmpty(option.summary()) ? View.GONE : View.VISIBLE);
return convertView;
}
}
}

View File

@@ -16,47 +16,51 @@
package com.android.settings.notification.modes;
import android.app.NotificationManager;
import android.app.settings.SettingsEnums;
import android.content.ComponentName;
import android.content.Context;
import android.service.notification.ConditionProviderService;
import android.content.Intent;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.annotation.VisibleForTesting;
import com.android.settings.R;
import com.android.settings.notification.modes.ZenModesListAddModePreferenceController.ModeType;
import com.android.settings.notification.modes.ZenModesListAddModePreferenceController.OnAddModeListener;
import com.android.settings.search.BaseSearchIndexProvider;
import com.android.settings.utils.ManagedServiceSettings;
import com.android.settings.utils.ZenServiceListing;
import com.android.settingslib.core.AbstractPreferenceController;
import com.android.settingslib.notification.modes.ZenMode;
import com.android.settingslib.notification.modes.ZenModesBackend;
import com.android.settingslib.search.SearchIndexable;
import com.google.common.collect.ImmutableList;
import java.util.List;
import java.util.Optional;
import java.util.Random;
@SearchIndexable
public class ZenModesListFragment extends ZenModesFragmentBase {
private static final ManagedServiceSettings.Config CONFIG = getConditionProviderConfig();
static final int REQUEST_NEW_MODE = 101;
@Nullable private ComponentName mActivityInvokedForAddNew;
@Nullable private ImmutableList<String> mZenModeIdsBeforeAddNew;
@Override
protected List<AbstractPreferenceController> createPreferenceControllers(Context context) {
ZenServiceListing serviceListing = new ZenServiceListing(getContext(), CONFIG);
serviceListing.reloadApprovedServices();
return buildPreferenceControllers(context, this, serviceListing);
return buildPreferenceControllers(context, this::onAvailableModeTypesForAdd);
}
private static List<AbstractPreferenceController> buildPreferenceControllers(Context context,
@Nullable Fragment parent, @Nullable ZenServiceListing serviceListing) {
OnAddModeListener onAddModeListener) {
// We need to redefine ZenModesBackend here even though mBackend exists so that this method
// can be static; it must be static to be able to be used in SEARCH_INDEX_DATA_PROVIDER.
ZenModesBackend backend = ZenModesBackend.getInstance(context);
return ImmutableList.of(
new ZenModesListPreferenceController(context, parent, backend),
new ZenModesListAddModePreferenceController(context, backend, serviceListing)
new ZenModesListPreferenceController(context, backend),
new ZenModesListAddModePreferenceController(context, onAddModeListener)
);
}
@@ -78,14 +82,55 @@ public class ZenModesListFragment extends ZenModesFragmentBase {
return SettingsEnums.NOTIFICATION_ZEN_MODE_AUTOMATION;
}
static ManagedServiceSettings.Config getConditionProviderConfig() {
return new ManagedServiceSettings.Config.Builder()
.setTag(TAG)
.setIntentAction(ConditionProviderService.SERVICE_INTERFACE)
.setConfigurationIntentAction(NotificationManager.ACTION_AUTOMATIC_ZEN_RULE)
.setPermission(android.Manifest.permission.BIND_CONDITION_PROVIDER_SERVICE)
.setNoun("condition provider")
.build();
private void onAvailableModeTypesForAdd(List<ModeType> types) {
if (types.size() > 1) {
// Show dialog to choose the mode to be created. Continue once the user chooses.
ZenModesListAddModeTypeChooserDialog.show(this, this::onChosenModeTypeForAdd, types);
} else {
// Will be custom_manual.
onChosenModeTypeForAdd(types.get(0));
}
}
@VisibleForTesting
void onChosenModeTypeForAdd(ModeType type) {
if (type.creationActivityIntent() != null) {
mActivityInvokedForAddNew = type.creationActivityIntent().getComponent();
mZenModeIdsBeforeAddNew = ImmutableList.copyOf(
mBackend.getModes().stream().map(ZenMode::getId).toList());
startActivityForResult(type.creationActivityIntent(), REQUEST_NEW_MODE);
} else {
// Custom-manual mode.
// TODO: b/326442408 - Transition to the choose-name-and-icon fragment.
ZenMode mode = mBackend.addCustomManualMode(
"Mode #" + new Random().nextInt(100), 0);
if (mode != null) {
ZenSubSettingLauncher.forMode(mContext, mode.getId()).launch();
}
}
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
// If coming back after starting a 3rd-party configuration activity to create a new mode,
// try to identify the created mode. Ideally this would be part of the resultCode/data, but
// the existing API doesn't work that way...
ComponentName activityInvoked = mActivityInvokedForAddNew;
ImmutableList<String> previousIds = mZenModeIdsBeforeAddNew;
mActivityInvokedForAddNew = null;
mZenModeIdsBeforeAddNew = null;
if (requestCode != REQUEST_NEW_MODE || previousIds == null || activityInvoked == null) {
return;
}
// If we find a new mode owned by the same package, presumably that's it. Open its page.
Optional<ZenMode> createdZenMode = mBackend.getModes().stream()
.filter(m -> !previousIds.contains(m.getId()))
.filter(m -> m.getRule().getPackageName().equals(activityInvoked.getPackageName()))
.findFirst();
createdZenMode.ifPresent(
mode -> ZenSubSettingLauncher.forMode(mContext, mode.getId()).launch());
}
/**
@@ -106,7 +151,7 @@ public class ZenModesListFragment extends ZenModesFragmentBase {
@Override
public List<AbstractPreferenceController> createPreferenceControllers(
Context context) {
return buildPreferenceControllers(context, null, null);
return buildPreferenceControllers(context, ignoredType -> {});
}
};
}

View File

@@ -20,8 +20,6 @@ import android.content.Context;
import android.content.res.Resources;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.preference.Preference;
import androidx.preference.PreferenceCategory;
@@ -43,14 +41,10 @@ import java.util.Map;
class ZenModesListPreferenceController extends BasePreferenceController {
protected static final String KEY = "zen_modes_list";
@Nullable
protected Fragment mParent;
protected ZenModesBackend mBackend;
public ZenModesListPreferenceController(Context context, @Nullable Fragment parent,
@NonNull ZenModesBackend backend) {
ZenModesListPreferenceController(Context context, @NonNull ZenModesBackend backend) {
super(context, KEY);
mParent = parent;
mBackend = backend;
}

View File

@@ -0,0 +1,170 @@
/*
* Copyright (C) 2024 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.modes;
import android.app.ActivityManager;
import android.app.NotificationManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.ComponentInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.service.notification.ConditionProviderService;
import android.util.ArraySet;
import android.util.Slog;
import androidx.annotation.Nullable;
import androidx.annotation.WorkerThread;
import com.android.settings.utils.ManagedServiceSettings;
import com.google.common.collect.ImmutableSet;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
class ZenServiceListing {
static final ManagedServiceSettings.Config CONFIGURATION =
new ManagedServiceSettings.Config.Builder()
.setTag("ZenServiceListing")
.setIntentAction(ConditionProviderService.SERVICE_INTERFACE)
.setConfigurationIntentAction(NotificationManager.ACTION_AUTOMATIC_ZEN_RULE)
.setPermission(android.Manifest.permission.BIND_CONDITION_PROVIDER_SERVICE)
.setNoun("condition provider")
.build();
private final Context mContext;
private final Set<ComponentInfo> mApprovedComponents = new ArraySet<>();
private final List<Callback> mZenCallbacks = new ArrayList<>();
private final NotificationManager mNm;
ZenServiceListing(Context context) {
mContext = context;
mNm = context.getSystemService(NotificationManager.class);
}
public ComponentInfo findService(final ComponentName cn) {
if (cn == null) {
return null;
}
for (ComponentInfo component : mApprovedComponents) {
final ComponentName ci = new ComponentName(component.packageName, component.name);
if (ci.equals(cn)) {
return component;
}
}
return null;
}
public void addZenCallback(Callback callback) {
mZenCallbacks.add(callback);
}
public void removeZenCallback(Callback callback) {
mZenCallbacks.remove(callback);
}
@WorkerThread
public ImmutableSet<ComponentInfo> loadApprovedComponents() {
return loadApprovedComponents(null);
}
@WorkerThread
public ImmutableSet<ComponentInfo> loadApprovedComponents(@Nullable String restrictToPkg) {
mApprovedComponents.clear();
List<String> enabledNotificationListenerPkgs = mNm.getEnabledNotificationListenerPackages();
List<ComponentInfo> components = new ArrayList<>();
getServices(CONFIGURATION, components, mContext.getPackageManager(), restrictToPkg);
getActivities(CONFIGURATION, components, mContext.getPackageManager(), restrictToPkg);
for (ComponentInfo componentInfo : components) {
final String pkg = componentInfo.getComponentName().getPackageName();
if (mNm.isNotificationPolicyAccessGrantedForPackage(pkg)
|| enabledNotificationListenerPkgs.contains(pkg)) {
mApprovedComponents.add(componentInfo);
}
}
if (!mApprovedComponents.isEmpty()) {
for (Callback callback : mZenCallbacks) {
callback.onComponentsReloaded(mApprovedComponents);
}
}
return ImmutableSet.copyOf(mApprovedComponents);
}
private static void getServices(ManagedServiceSettings.Config c, List<ComponentInfo> list,
PackageManager pm, @Nullable String restrictToPkg) {
final int user = ActivityManager.getCurrentUser();
Intent queryIntent = new Intent(c.intentAction);
if (restrictToPkg != null) {
queryIntent.setPackage(restrictToPkg);
}
List<ResolveInfo> installedServices = pm.queryIntentServicesAsUser(
queryIntent,
PackageManager.GET_SERVICES | PackageManager.GET_META_DATA,
user);
for (int i = 0, count = installedServices.size(); i < count; i++) {
ResolveInfo resolveInfo = installedServices.get(i);
ServiceInfo info = resolveInfo.serviceInfo;
if (!c.permission.equals(info.permission)) {
Slog.w(c.tag, "Skipping " + c.noun + " service "
+ info.packageName + "/" + info.name
+ ": it does not require the permission "
+ c.permission);
continue;
}
if (list != null) {
list.add(info);
}
}
}
private static void getActivities(ManagedServiceSettings.Config c, List<ComponentInfo> list,
PackageManager pm, @Nullable String restrictToPkg) {
final int user = ActivityManager.getCurrentUser();
Intent queryIntent = new Intent(c.configIntentAction);
if (restrictToPkg != null) {
queryIntent.setPackage(restrictToPkg);
}
List<ResolveInfo> resolveInfos = pm.queryIntentActivitiesAsUser(
queryIntent,
PackageManager.GET_ACTIVITIES | PackageManager.GET_META_DATA,
user);
for (int i = 0, count = resolveInfos.size(); i < count; i++) {
ResolveInfo resolveInfo = resolveInfos.get(i);
ActivityInfo info = resolveInfo.activityInfo;
if (list != null) {
list.add(info);
}
}
}
public interface Callback {
void onComponentsReloaded(Set<ComponentInfo> components);
}
}