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:
committed by
Android (Google) Code Review
commit
0037dfe9a9
@@ -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() + ")");
|
||||
|
@@ -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;
|
||||
}
|
||||
}
|
@@ -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)));
|
||||
}
|
||||
}
|
||||
|
@@ -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
|
||||
|
@@ -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);
|
||||
}
|
||||
|
||||
|
@@ -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);
|
||||
}
|
||||
|
||||
|
@@ -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(
|
||||
|
@@ -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(
|
||||
|
@@ -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;
|
||||
}
|
||||
|
||||
|
@@ -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 =
|
||||
|
@@ -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());
|
||||
}
|
||||
|
@@ -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);
|
||||
}
|
||||
|
||||
|
@@ -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);
|
||||
}
|
||||
|
||||
|
@@ -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);
|
||||
}
|
||||
|
||||
|
@@ -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);
|
||||
}
|
||||
|
||||
|
@@ -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;
|
||||
}
|
||||
|
||||
|
@@ -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);
|
||||
}
|
||||
|
||||
|
@@ -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;
|
||||
}
|
||||
}
|
||||
|
@@ -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;
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
||||
|
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@@ -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 -> {});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@@ -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;
|
||||
}
|
||||
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user