Merge "Add icon picker for zen modes" into main
This commit is contained in:
committed by
Android (Google) Code Review
commit
ec5b25eb48
69
src/com/android/settings/notification/modes/IconUtil.java
Normal file
69
src/com/android/settings/notification/modes/IconUtil.java
Normal file
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* 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 android.content.Context;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.graphics.drawable.LayerDrawable;
|
||||
import android.graphics.drawable.ShapeDrawable;
|
||||
import android.graphics.drawable.shapes.OvalShape;
|
||||
|
||||
import androidx.annotation.DrawableRes;
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.android.settings.R;
|
||||
import com.android.settingslib.Utils;
|
||||
|
||||
class IconUtil {
|
||||
|
||||
static Drawable applyTint(@NonNull Context context, @NonNull Drawable icon) {
|
||||
icon = icon.mutate();
|
||||
icon.setTintList(
|
||||
Utils.getColorAttr(context, android.R.attr.colorControlNormal));
|
||||
return icon;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
static Drawable makeIconCircle(@NonNull Context context, @NonNull Drawable icon) {
|
||||
ShapeDrawable background = new ShapeDrawable(new OvalShape());
|
||||
background.getPaint().setColor(Utils.getColorAttrDefaultColor(context,
|
||||
com.android.internal.R.attr.materialColorSecondaryContainer));
|
||||
icon.setTint(Utils.getColorAttrDefaultColor(context,
|
||||
com.android.internal.R.attr.materialColorOnSecondaryContainer));
|
||||
|
||||
LayerDrawable layerDrawable = new LayerDrawable(new Drawable[] { background, icon });
|
||||
|
||||
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);
|
||||
|
||||
return layerDrawable;
|
||||
}
|
||||
|
||||
static Drawable makeIconCircle(@NonNull Context context, @DrawableRes int iconResId) {
|
||||
return makeIconCircle(context, checkNotNull(context.getDrawable(iconResId)));
|
||||
}
|
||||
}
|
||||
@@ -204,6 +204,14 @@ class ZenMode {
|
||||
: new ZenDeviceEffects.Builder().build();
|
||||
}
|
||||
|
||||
public boolean canEditName() {
|
||||
return !isManualDnd();
|
||||
}
|
||||
|
||||
public boolean canEditIcon() {
|
||||
return !isManualDnd();
|
||||
}
|
||||
|
||||
public boolean canBeDeleted() {
|
||||
return !mIsManualDnd;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* 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.android.settings.notification.modes.ZenModeFragmentBase.MODE_ID;
|
||||
|
||||
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.widget.ActionButtonsPreference;
|
||||
|
||||
class ZenModeActionsPreferenceController extends AbstractZenModePreferenceController {
|
||||
|
||||
private ActionButtonsPreference mPreference;
|
||||
|
||||
ZenModeActionsPreferenceController(@NonNull Context context, @NonNull String key,
|
||||
@Nullable ZenModesBackend backend) {
|
||||
super(context, key, backend);
|
||||
}
|
||||
|
||||
@Override
|
||||
void updateState(Preference preference, @NonNull ZenMode zenMode) {
|
||||
ActionButtonsPreference buttonsPreference = (ActionButtonsPreference) preference;
|
||||
|
||||
// TODO: b/346278854 - Add rename action (with setButton1Enabled(zenMode.canEditName())
|
||||
buttonsPreference.setButton1Text(R.string.zen_mode_action_change_name);
|
||||
buttonsPreference.setButton1Icon(R.drawable.ic_mode_edit);
|
||||
buttonsPreference.setButton1Enabled(false);
|
||||
|
||||
buttonsPreference.setButton2Text(R.string.zen_mode_action_change_icon);
|
||||
buttonsPreference.setButton2Icon(R.drawable.ic_zen_mode_action_change_icon);
|
||||
buttonsPreference.setButton2Enabled(zenMode.canEditIcon());
|
||||
buttonsPreference.setButton2OnClickListener(v -> {
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putString(MODE_ID, zenMode.getId());
|
||||
new SubSettingLauncher(mContext)
|
||||
.setDestination(ZenModeIconPickerFragment.class.getName())
|
||||
// TODO: b/332937635 - Update metrics category
|
||||
.setSourceMetricsCategory(0)
|
||||
.setArguments(bundle)
|
||||
.launch();
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -38,6 +38,7 @@ public class ZenModeFragment extends ZenModeFragmentBase {
|
||||
List<AbstractPreferenceController> prefControllers = new ArrayList<>();
|
||||
prefControllers.add(new ZenModeHeaderController(context, "header", this, mBackend));
|
||||
prefControllers.add(new ZenModeButtonPreferenceController(context, "activate", mBackend));
|
||||
prefControllers.add(new ZenModeActionsPreferenceController(context, "actions", mBackend));
|
||||
prefControllers.add(new ZenModePeopleLinkPreferenceController(
|
||||
context, "zen_mode_people", mBackend));
|
||||
prefControllers.add(new ZenModeAppsLinkPreferenceController(
|
||||
|
||||
@@ -51,12 +51,12 @@ abstract class ZenModeFragmentBase extends ZenModesFragmentBase {
|
||||
if (bundle != null && bundle.containsKey(MODE_ID)) {
|
||||
String id = bundle.getString(MODE_ID);
|
||||
if (!reloadMode(id)) {
|
||||
Log.d(TAG, "Mode id " + id + " not found");
|
||||
Log.e(TAG, "Mode id " + id + " not found");
|
||||
toastAndFinish();
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
Log.d(TAG, "Mode id required to set mode config settings");
|
||||
Log.e(TAG, "Mode id required to set mode config settings");
|
||||
toastAndFinish();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -63,9 +63,8 @@ class ZenModeHeaderController extends AbstractZenModePreferenceController {
|
||||
|
||||
FutureUtil.whenDone(
|
||||
zenMode.getIcon(mContext, IconLoader.getInstance()),
|
||||
icon -> mHeaderController.setIcon(icon)
|
||||
.setLabel(zenMode.getRule().getName())
|
||||
.done(false /* rebindActions */),
|
||||
icon -> mHeaderController.setIcon(IconUtil.applyTint(mContext, icon))
|
||||
.done(/* rebindActions= */ false),
|
||||
mContext.getMainExecutor());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* 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.settings.SettingsEnums;
|
||||
import android.content.Context;
|
||||
|
||||
import com.android.settings.R;
|
||||
import com.android.settingslib.core.AbstractPreferenceController;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class ZenModeIconPickerFragment extends ZenModeFragmentBase {
|
||||
@Override
|
||||
protected int getPreferenceScreenResId() {
|
||||
return R.xml.modes_icon_picker;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMetricsCategory() {
|
||||
// TODO: b/332937635 - make this the correct metrics category
|
||||
return SettingsEnums.NOTIFICATION_ZEN_MODE_AUTOMATION;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<AbstractPreferenceController> createPreferenceControllers(Context context) {
|
||||
return ImmutableList.of(
|
||||
new ZenModeIconPickerIconPreferenceController(context, "current_icon", this,
|
||||
mBackend),
|
||||
new ZenModeIconPickerListPreferenceController(context, "icon_list", this,
|
||||
mBackend));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* 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.content.Context;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.preference.Preference;
|
||||
|
||||
import com.android.settings.R;
|
||||
import com.android.settings.dashboard.DashboardFragment;
|
||||
import com.android.settings.widget.EntityHeaderController;
|
||||
import com.android.settingslib.widget.LayoutPreference;
|
||||
|
||||
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);
|
||||
mFragment = fragment;
|
||||
}
|
||||
|
||||
@Override
|
||||
void updateState(Preference preference, @NonNull ZenMode zenMode) {
|
||||
preference.setSelectable(false);
|
||||
|
||||
if (mHeaderController == null) {
|
||||
final LayoutPreference pref = (LayoutPreference) preference;
|
||||
mHeaderController = EntityHeaderController.newInstance(
|
||||
mFragment.getActivity(),
|
||||
mFragment,
|
||||
pref.findViewById(R.id.entity_header));
|
||||
}
|
||||
|
||||
FutureUtil.whenDone(
|
||||
zenMode.getIcon(mContext, IconLoader.getInstance()),
|
||||
icon -> mHeaderController.setIcon(IconUtil.applyTint(mContext, icon))
|
||||
.done(/* rebindActions= */ false),
|
||||
mContext.getMainExecutor());
|
||||
}
|
||||
}
|
||||
@@ -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.content.Context;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
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;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.android.settings.R;
|
||||
import com.android.settings.dashboard.DashboardFragment;
|
||||
import com.android.settingslib.widget.LayoutPreference;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
class ZenModeIconPickerListPreferenceController extends AbstractZenModePreferenceController {
|
||||
|
||||
private final DashboardFragment mFragment;
|
||||
private IconAdapter mAdapter;
|
||||
|
||||
ZenModeIconPickerListPreferenceController(@NonNull Context context, @NonNull String key,
|
||||
@NonNull DashboardFragment fragment, @Nullable ZenModesBackend backend) {
|
||||
super(context, key, backend);
|
||||
mFragment = fragment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void displayPreference(PreferenceScreen screen) {
|
||||
super.displayPreference(screen);
|
||||
LayoutPreference pref = screen.findPreference(getPreferenceKey());
|
||||
if (pref == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mAdapter == null) {
|
||||
// TODO: b/333901673 - This is just an example; replace with correct list.
|
||||
List<IconInfo> exampleIcons =
|
||||
Arrays.stream(android.R.drawable.class.getFields())
|
||||
.filter(
|
||||
f -> Modifier.isStatic(f.getModifiers())
|
||||
&& f.getName().startsWith("ic_"))
|
||||
.sorted(Comparator.comparing(Field::getName))
|
||||
.limit(20)
|
||||
.map(f -> {
|
||||
try {
|
||||
return new IconInfo(f.getInt(null), f.getName());
|
||||
} catch (IllegalAccessException e) {
|
||||
return null;
|
||||
}
|
||||
})
|
||||
.filter(Objects::nonNull)
|
||||
.toList();
|
||||
mAdapter = new IconAdapter(exampleIcons);
|
||||
}
|
||||
RecyclerView recyclerView = pref.findViewById(R.id.icon_list);
|
||||
recyclerView.setLayoutManager(new AutoFitGridLayoutManager(mContext));
|
||||
recyclerView.setAdapter(mAdapter);
|
||||
recyclerView.setHasFixedSize(true);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
void onIconSelected(@DrawableRes int resId) {
|
||||
saveMode(mode -> {
|
||||
mode.getRule().setIconResId(resId);
|
||||
return mode;
|
||||
});
|
||||
mFragment.finish();
|
||||
}
|
||||
|
||||
@Override
|
||||
void updateState(Preference preference, @NonNull ZenMode zenMode) {
|
||||
// Nothing to do, the current icon is shown in a different preference.
|
||||
}
|
||||
|
||||
private record IconInfo(@DrawableRes int resId, String description) { }
|
||||
|
||||
private class IconHolder extends RecyclerView.ViewHolder {
|
||||
|
||||
private final ImageView mImageView;
|
||||
|
||||
IconHolder(@NonNull View itemView) {
|
||||
super(itemView);
|
||||
mImageView = itemView.findViewById(R.id.icon_image_view);
|
||||
}
|
||||
|
||||
void bindIcon(IconInfo icon) {
|
||||
mImageView.setImageDrawable(
|
||||
IconUtil.makeIconCircle(itemView.getContext(), icon.resId()));
|
||||
itemView.setContentDescription(icon.description());
|
||||
itemView.setOnClickListener(v -> onIconSelected(icon.resId()));
|
||||
}
|
||||
}
|
||||
|
||||
private class IconAdapter extends RecyclerView.Adapter<IconHolder> {
|
||||
|
||||
private final ImmutableList<IconInfo> mIconResources;
|
||||
|
||||
private IconAdapter(List<IconInfo> iconOptions) {
|
||||
mIconResources = ImmutableList.copyOf(iconOptions);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public IconHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
View itemView = LayoutInflater.from(parent.getContext()).inflate(
|
||||
R.layout.modes_icon_list_item, parent, false);
|
||||
return new IconHolder(itemView);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull IconHolder holder, int position) {
|
||||
holder.bindIcon(mIconResources.get(position));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return mIconResources.size();
|
||||
}
|
||||
}
|
||||
|
||||
private static class AutoFitGridLayoutManager extends GridLayoutManager {
|
||||
private final float mColumnWidth;
|
||||
|
||||
AutoFitGridLayoutManager(Context context) {
|
||||
super(context, /* spanCount= */ 1);
|
||||
this.mColumnWidth = context
|
||||
.getResources()
|
||||
.getDimensionPixelSize(R.dimen.zen_mode_icon_list_item_size);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
|
||||
final int totalSpace = getWidth() - getPaddingRight() - getPaddingLeft();
|
||||
final int spanCount = Math.max(1, (int) (totalSpace / mColumnWidth));
|
||||
setSpanCount(spanCount);
|
||||
super.onLayoutChildren(recycler, state);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -23,7 +23,6 @@ import android.os.Bundle;
|
||||
|
||||
import com.android.settings.core.SubSettingLauncher;
|
||||
import com.android.settingslib.RestrictedPreference;
|
||||
import com.android.settingslib.Utils;
|
||||
|
||||
/**
|
||||
* Preference representing a single mode item on the modes aggregator page. Clicking on this
|
||||
@@ -59,11 +58,7 @@ class ZenModeListPreference extends RestrictedPreference {
|
||||
|
||||
FutureUtil.whenDone(
|
||||
mZenMode.getIcon(mContext, IconLoader.getInstance()),
|
||||
icon -> {
|
||||
icon.setTintList(
|
||||
Utils.getColorAttr(mContext, android.R.attr.colorControlNormal));
|
||||
setIcon(icon);
|
||||
},
|
||||
icon -> setIcon(IconUtil.applyTint(mContext, icon)),
|
||||
mContext.getMainExecutor());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user