Add icon picker for zen modes
Bug: 333901673 Test: atest ZenModeIconPickerListPreferenceControllerTest Flag: android.app.modes_ui Change-Id: Ib6faaac7c93b25423d18dbdcd0a2310de15fa86f
This commit is contained in:
25
res/drawable/ic_zen_mode_action_change_icon.xml
Normal file
25
res/drawable/ic_zen_mode_action_change_icon.xml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
<!--
|
||||||
|
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.
|
||||||
|
-->
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:tint="?android:attr/colorControlNormal"
|
||||||
|
android:viewportHeight="960"
|
||||||
|
android:viewportWidth="960">
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M620,440Q645,440 662.5,422.5Q680,405 680,380Q680,355 662.5,337.5Q645,320 620,320Q595,320 577.5,337.5Q560,355 560,380Q560,405 577.5,422.5Q595,440 620,440ZM340,440Q365,440 382.5,422.5Q400,405 400,380Q400,355 382.5,337.5Q365,320 340,320Q315,320 297.5,337.5Q280,355 280,380Q280,405 297.5,422.5Q315,440 340,440ZM480,700Q548,700 603.5,661.5Q659,623 684,560L276,560Q301,623 356.5,661.5Q412,700 480,700ZM480,880Q397,880 324,848.5Q251,817 197,763Q143,709 111.5,636Q80,563 80,480Q80,397 111.5,324Q143,251 197,197Q251,143 324,111.5Q397,80 480,80Q563,80 636,111.5Q709,143 763,197Q817,251 848.5,324Q880,397 880,480Q880,563 848.5,636Q817,709 763,763Q709,817 636,848.5Q563,880 480,880ZM480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480ZM480,800Q614,800 707,707Q800,614 800,480Q800,346 707,253Q614,160 480,160Q346,160 253,253Q160,346 160,480Q160,614 253,707Q346,800 480,800Z" />
|
||||||
|
</vector>
|
38
res/layout/modes_icon_list.xml
Normal file
38
res/layout/modes_icon_list.xml
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
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.
|
||||||
|
-->
|
||||||
|
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/icon_list"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:clipToPadding="true"
|
||||||
|
android:nestedScrollingEnabled="false"
|
||||||
|
android:paddingStart="12dp"
|
||||||
|
android:paddingEnd="12dp"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"/>
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
31
res/layout/modes_icon_list_item.xml
Normal file
31
res/layout/modes_icon_list_item.xml
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
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.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="@dimen/zen_mode_icon_list_item_size"
|
||||||
|
android:clickable="true">
|
||||||
|
<!-- width is match_parent to distribute remaining horizontal space -->
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/icon_image_view"
|
||||||
|
android:layout_width="@dimen/zen_mode_icon_list_circle_diameter"
|
||||||
|
android:layout_height="@dimen/zen_mode_icon_list_circle_diameter"
|
||||||
|
android:importantForAccessibility="no"
|
||||||
|
android:layout_gravity="center" />
|
||||||
|
</FrameLayout>
|
@@ -491,4 +491,9 @@
|
|||||||
|
|
||||||
<dimen name="audio_streams_qrcode_size">264dp</dimen>
|
<dimen name="audio_streams_qrcode_size">264dp</dimen>
|
||||||
<dimen name="audio_streams_qrcode_preview_radius">30dp</dimen>
|
<dimen name="audio_streams_qrcode_preview_radius">30dp</dimen>
|
||||||
|
|
||||||
|
<!-- Zen Modes -->
|
||||||
|
<dimen name="zen_mode_icon_list_item_size">96dp</dimen>
|
||||||
|
<dimen name="zen_mode_icon_list_circle_diameter">56dp</dimen>
|
||||||
|
<dimen name="zen_mode_icon_list_icon_size">32dp</dimen>
|
||||||
</resources>
|
</resources>
|
||||||
|
@@ -9309,6 +9309,15 @@
|
|||||||
<!-- [CHAR LIMIT=NONE] Zen mode summary spoken when changing mode by voice: Turn on all notifications. -->
|
<!-- [CHAR LIMIT=NONE] Zen mode summary spoken when changing mode by voice: Turn on all notifications. -->
|
||||||
<string name="zen_mode_summary_always">Change to always interrupt</string>
|
<string name="zen_mode_summary_always">Change to always interrupt</string>
|
||||||
|
|
||||||
|
<!-- [CHAR LIMIT=20] Caption of the action button to change the name of a mode. -->
|
||||||
|
<string name="zen_mode_action_change_name">Rename</string>
|
||||||
|
|
||||||
|
<!-- [CHAR LIMIT=20] Caption of the action button to change the icon of a mode. -->
|
||||||
|
<string name="zen_mode_action_change_icon">Change icon</string>
|
||||||
|
|
||||||
|
<!-- [CHAR LIMIT=40] Zen mode settings: Title for the "choose mode icon" screen -->
|
||||||
|
<string name="zen_mode_icon_picker_title">Change icon</string>
|
||||||
|
|
||||||
<!-- Content description for help icon button [CHAR LIMIT=20] -->
|
<!-- Content description for help icon button [CHAR LIMIT=20] -->
|
||||||
<string name="warning_button_text">Warning</string>
|
<string name="warning_button_text">Warning</string>
|
||||||
|
|
||||||
|
37
res/xml/modes_icon_picker.xml
Normal file
37
res/xml/modes_icon_picker.xml
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
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.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<PreferenceScreen
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:settings="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:key="zen_mode_icon_picker_page"
|
||||||
|
settings:searchable="false"
|
||||||
|
android:title="@string/zen_mode_icon_picker_title">
|
||||||
|
|
||||||
|
<com.android.settingslib.widget.LayoutPreference
|
||||||
|
android:key="current_icon"
|
||||||
|
android:layout="@layout/settings_entity_header" />
|
||||||
|
|
||||||
|
<com.android.settings.applications.SpacePreference
|
||||||
|
android:layout_height="16dp" />
|
||||||
|
|
||||||
|
<com.android.settingslib.widget.LayoutPreference
|
||||||
|
android:key="icon_list"
|
||||||
|
android:selectable="false"
|
||||||
|
android:layout="@layout/modes_icon_list"/>
|
||||||
|
|
||||||
|
</PreferenceScreen>
|
@@ -28,6 +28,10 @@
|
|||||||
android:selectable="false"
|
android:selectable="false"
|
||||||
android:layout="@layout/modes_activation_button"/>
|
android:layout="@layout/modes_activation_button"/>
|
||||||
|
|
||||||
|
<com.android.settingslib.widget.ActionButtonsPreference
|
||||||
|
android:key="actions"
|
||||||
|
android:selectable="true" />
|
||||||
|
|
||||||
<PreferenceCategory
|
<PreferenceCategory
|
||||||
android:title="@string/mode_interruption_filter_title"
|
android:title="@string/mode_interruption_filter_title"
|
||||||
android:key="modes_filters">
|
android:key="modes_filters">
|
||||||
|
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();
|
: new ZenDeviceEffects.Builder().build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean canEditName() {
|
||||||
|
return !isManualDnd();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean canEditIcon() {
|
||||||
|
return !isManualDnd();
|
||||||
|
}
|
||||||
|
|
||||||
public boolean canBeDeleted() {
|
public boolean canBeDeleted() {
|
||||||
return !mIsManualDnd;
|
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<>();
|
List<AbstractPreferenceController> prefControllers = new ArrayList<>();
|
||||||
prefControllers.add(new ZenModeHeaderController(context, "header", this, mBackend));
|
prefControllers.add(new ZenModeHeaderController(context, "header", this, mBackend));
|
||||||
prefControllers.add(new ZenModeButtonPreferenceController(context, "activate", mBackend));
|
prefControllers.add(new ZenModeButtonPreferenceController(context, "activate", mBackend));
|
||||||
|
prefControllers.add(new ZenModeActionsPreferenceController(context, "actions", mBackend));
|
||||||
prefControllers.add(new ZenModePeopleLinkPreferenceController(
|
prefControllers.add(new ZenModePeopleLinkPreferenceController(
|
||||||
context, "zen_mode_people", mBackend));
|
context, "zen_mode_people", mBackend));
|
||||||
prefControllers.add(new ZenModeAppsLinkPreferenceController(
|
prefControllers.add(new ZenModeAppsLinkPreferenceController(
|
||||||
|
@@ -51,12 +51,12 @@ abstract class ZenModeFragmentBase extends ZenModesFragmentBase {
|
|||||||
if (bundle != null && bundle.containsKey(MODE_ID)) {
|
if (bundle != null && bundle.containsKey(MODE_ID)) {
|
||||||
String id = bundle.getString(MODE_ID);
|
String id = bundle.getString(MODE_ID);
|
||||||
if (!reloadMode(id)) {
|
if (!reloadMode(id)) {
|
||||||
Log.d(TAG, "Mode id " + id + " not found");
|
Log.e(TAG, "Mode id " + id + " not found");
|
||||||
toastAndFinish();
|
toastAndFinish();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Log.d(TAG, "Mode id required to set mode config settings");
|
Log.e(TAG, "Mode id required to set mode config settings");
|
||||||
toastAndFinish();
|
toastAndFinish();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@@ -63,9 +63,8 @@ class ZenModeHeaderController extends AbstractZenModePreferenceController {
|
|||||||
|
|
||||||
FutureUtil.whenDone(
|
FutureUtil.whenDone(
|
||||||
zenMode.getIcon(mContext, IconLoader.getInstance()),
|
zenMode.getIcon(mContext, IconLoader.getInstance()),
|
||||||
icon -> mHeaderController.setIcon(icon)
|
icon -> mHeaderController.setIcon(IconUtil.applyTint(mContext, icon))
|
||||||
.setLabel(zenMode.getRule().getName())
|
.done(/* rebindActions= */ false),
|
||||||
.done(false /* rebindActions */),
|
|
||||||
mContext.getMainExecutor());
|
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.settings.core.SubSettingLauncher;
|
||||||
import com.android.settingslib.RestrictedPreference;
|
import com.android.settingslib.RestrictedPreference;
|
||||||
import com.android.settingslib.Utils;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Preference representing a single mode item on the modes aggregator page. Clicking on this
|
* Preference representing a single mode item on the modes aggregator page. Clicking on this
|
||||||
@@ -59,11 +58,7 @@ class ZenModeListPreference extends RestrictedPreference {
|
|||||||
|
|
||||||
FutureUtil.whenDone(
|
FutureUtil.whenDone(
|
||||||
mZenMode.getIcon(mContext, IconLoader.getInstance()),
|
mZenMode.getIcon(mContext, IconLoader.getInstance()),
|
||||||
icon -> {
|
icon -> setIcon(IconUtil.applyTint(mContext, icon)),
|
||||||
icon.setTintList(
|
|
||||||
Utils.getColorAttr(mContext, android.R.attr.colorControlNormal));
|
|
||||||
setIcon(icon);
|
|
||||||
},
|
|
||||||
mContext.getMainExecutor());
|
mContext.getMainExecutor());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,91 @@
|
|||||||
|
/*
|
||||||
|
* 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.truth.Truth.assertThat;
|
||||||
|
|
||||||
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
import android.app.AutomaticZenRule;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.net.Uri;
|
||||||
|
|
||||||
|
import androidx.preference.PreferenceScreen;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
|
import com.android.settings.R;
|
||||||
|
import com.android.settings.dashboard.DashboardFragment;
|
||||||
|
import com.android.settingslib.widget.LayoutPreference;
|
||||||
|
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.mockito.ArgumentCaptor;
|
||||||
|
import org.robolectric.RobolectricTestRunner;
|
||||||
|
import org.robolectric.RuntimeEnvironment;
|
||||||
|
|
||||||
|
@RunWith(RobolectricTestRunner.class)
|
||||||
|
public class ZenModeIconPickerListPreferenceControllerTest {
|
||||||
|
|
||||||
|
private static final ZenMode ZEN_MODE = new ZenMode(
|
||||||
|
"mode_id",
|
||||||
|
new AutomaticZenRule.Builder("mode name", Uri.parse("mode")).build(),
|
||||||
|
/* isActive= */ false);
|
||||||
|
|
||||||
|
private ZenModesBackend mBackend;
|
||||||
|
private ZenModeIconPickerListPreferenceController mController;
|
||||||
|
private PreferenceScreen mPreferenceScreen;
|
||||||
|
private RecyclerView mRecyclerView;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() {
|
||||||
|
Context context = RuntimeEnvironment.getApplication();
|
||||||
|
mBackend = mock(ZenModesBackend.class);
|
||||||
|
|
||||||
|
DashboardFragment fragment = mock(DashboardFragment.class);
|
||||||
|
mController = new ZenModeIconPickerListPreferenceController(
|
||||||
|
RuntimeEnvironment.getApplication(), "icon_list", fragment, mBackend);
|
||||||
|
|
||||||
|
mRecyclerView = new RecyclerView(context);
|
||||||
|
mRecyclerView.setId(R.id.icon_list);
|
||||||
|
LayoutPreference layoutPreference = new LayoutPreference(context, mRecyclerView);
|
||||||
|
mPreferenceScreen = mock(PreferenceScreen.class);
|
||||||
|
when(mPreferenceScreen.findPreference(eq("icon_list"))).thenReturn(layoutPreference);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void displayPreference_loadsIcons() {
|
||||||
|
mController.displayPreference(mPreferenceScreen);
|
||||||
|
|
||||||
|
assertThat(mRecyclerView.getAdapter()).isNotNull();
|
||||||
|
assertThat(mRecyclerView.getAdapter().getItemCount()).isEqualTo(20);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void selectIcon_updatesMode() {
|
||||||
|
mController.setZenMode(ZEN_MODE);
|
||||||
|
|
||||||
|
mController.onIconSelected(R.drawable.ic_android);
|
||||||
|
|
||||||
|
ArgumentCaptor<ZenMode> captor = ArgumentCaptor.forClass(ZenMode.class);
|
||||||
|
verify(mBackend).updateMode(captor.capture());
|
||||||
|
assertThat(captor.getValue().getRule().getIconResId()).isEqualTo(R.drawable.ic_android);
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user