1 /*
2  * Copyright 2024 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.car.settings.display;
18 
19 import static android.car.settings.CarSettings.Global.FORCED_DAY_NIGHT_MODE;
20 
21 import android.car.drivingstate.CarUxRestrictions;
22 import android.car.settings.CarSettings;
23 import android.content.Context;
24 import android.database.ContentObserver;
25 import android.graphics.drawable.Drawable;
26 import android.net.Uri;
27 import android.os.Handler;
28 import android.os.Looper;
29 import android.provider.Settings;
30 
31 import androidx.annotation.VisibleForTesting;
32 
33 import com.android.car.settings.Flags;
34 import com.android.car.settings.R;
35 import com.android.car.settings.common.BaseActionItem;
36 import com.android.car.settings.common.FragmentController;
37 import com.android.car.settings.common.MultiActionPreference;
38 import com.android.car.settings.common.PreferenceController;
39 import com.android.car.settings.common.ToggleButtonActionItem;
40 
41 /**
42  * PreferenceController for the day/night theme mode.
43  */
44 public class ThemeTogglePreferenceController extends PreferenceController<MultiActionPreference> {
45     static final int FORCED_SENSOR_MODE = 0;
46     static final int FORCED_DAY_MODE = 1;
47     static final int FORCED_NIGHT_MODE = 2;
48 
49     private static final Uri THEME_MODE_URI = Settings.Global.getUriFor(
50             CarSettings.Global.FORCED_DAY_NIGHT_MODE);
51     private final Handler mHandler = new Handler(Looper.getMainLooper());
52     private final ContentObserver mThemeToggleObserver = new ContentObserver(mHandler) {
53         @Override
54         public void onChange(boolean selfChange) {
55             refreshUi();
56         }
57     };
58 
ThemeTogglePreferenceController(Context context, String preferenceKey, FragmentController fragmentController, CarUxRestrictions uxRestrictions)59     public ThemeTogglePreferenceController(Context context, String preferenceKey,
60             FragmentController fragmentController,
61             CarUxRestrictions uxRestrictions) {
62         super(context, preferenceKey, fragmentController, uxRestrictions);
63     }
64 
65     @Override
onCreateInternal()66     protected void onCreateInternal() {
67         super.onCreateInternal();
68         getPreference().setSelectable(false);
69         if (getAutoButton() != null) {
70             getAutoButton().setDrawable(getThemeModeIcon(FORCED_SENSOR_MODE));
71             getAutoButton().setOnClickListener(isChecked -> {
72                 if (isChecked) {
73                     setForcedDayNightModeSetting(FORCED_SENSOR_MODE);
74                 }
75                 refreshUi();
76             });
77         }
78         if (getDayButton() != null) {
79             getDayButton().setDrawable(getThemeModeIcon(FORCED_DAY_MODE));
80             getDayButton().setOnClickListener(isChecked -> {
81                 if (isChecked) {
82                     setForcedDayNightModeSetting(FORCED_DAY_MODE);
83                 }
84                 refreshUi();
85             });
86         }
87         if (getNightButton() != null) {
88             getNightButton().setDrawable(getThemeModeIcon(FORCED_NIGHT_MODE));
89             getNightButton().setOnClickListener(isChecked -> {
90                 if (isChecked) {
91                     setForcedDayNightModeSetting(FORCED_NIGHT_MODE);
92                 }
93                 refreshUi();
94             });
95         }
96     }
97 
98     @Override
onStartInternal()99     protected void onStartInternal() {
100         super.onStartInternal();
101         getContext().getContentResolver().registerContentObserver(THEME_MODE_URI,
102                 /* notifyForDescendants= */ false, mThemeToggleObserver);
103     }
104 
105     @Override
onStopInternal()106     protected void onStopInternal() {
107         super.onStopInternal();
108         getContext().getContentResolver().unregisterContentObserver(mThemeToggleObserver);
109     }
110 
111     @Override
updateState(MultiActionPreference preference)112     protected void updateState(MultiActionPreference preference) {
113         if (getAutoButton() != null) {
114             boolean checked = getForcedDayNightModeSetting() == FORCED_SENSOR_MODE;
115             getAutoButton().setChecked(checked);
116             getAutoButton().setEnabled(!checked);
117         }
118         if (getDayButton() != null) {
119             boolean checked = getForcedDayNightModeSetting() == FORCED_DAY_MODE;
120             getDayButton().setChecked(checked);
121             getDayButton().setEnabled(!checked);
122         }
123         if (getNightButton() != null) {
124             boolean checked = getForcedDayNightModeSetting() == FORCED_NIGHT_MODE;
125             getNightButton().setChecked(checked);
126             getNightButton().setEnabled(!checked);
127         }
128     }
129 
130     @Override
getPreferenceType()131     protected Class<MultiActionPreference> getPreferenceType() {
132         return MultiActionPreference.class;
133     }
134 
135     @Override
getDefaultAvailabilityStatus()136     public int getDefaultAvailabilityStatus() {
137         if (!android.car.feature.Flags.carNightGlobalSetting() || !Flags.uiThemeToggle()) {
138             return UNSUPPORTED_ON_DEVICE;
139         }
140         return AVAILABLE;
141     }
142 
143     @VisibleForTesting
getAutoButton()144     ToggleButtonActionItem getAutoButton() {
145         BaseActionItem item = getPreference().getActionItem(
146                 MultiActionPreference.ActionItem.ACTION_ITEM1);
147         if (item instanceof ToggleButtonActionItem) {
148             return (ToggleButtonActionItem) item;
149         }
150         return null;
151     }
152 
153     @VisibleForTesting
getDayButton()154     ToggleButtonActionItem getDayButton() {
155         BaseActionItem item = getPreference().getActionItem(
156                 MultiActionPreference.ActionItem.ACTION_ITEM2);
157         if (item instanceof ToggleButtonActionItem) {
158             return (ToggleButtonActionItem) item;
159         }
160         return null;
161     }
162 
163     @VisibleForTesting
getNightButton()164     ToggleButtonActionItem getNightButton() {
165         BaseActionItem item = getPreference().getActionItem(
166                 MultiActionPreference.ActionItem.ACTION_ITEM3);
167         if (item instanceof ToggleButtonActionItem) {
168             return (ToggleButtonActionItem) item;
169         }
170         return null;
171     }
172 
getForcedDayNightModeSetting()173     private int getForcedDayNightModeSetting() {
174         return Settings.Global.getInt(getContext().getContentResolver(),
175                 FORCED_DAY_NIGHT_MODE, FORCED_SENSOR_MODE);
176     }
177 
setForcedDayNightModeSetting(int mode)178     private void setForcedDayNightModeSetting(int mode) {
179         Settings.Global.putInt(getContext().getContentResolver(), FORCED_DAY_NIGHT_MODE, mode);
180     }
181 
getThemeModeIcon(int mode)182     private Drawable getThemeModeIcon(int mode) {
183         return switch (mode) {
184             case FORCED_SENSOR_MODE -> getContext().getDrawable(
185                     R.drawable.ic_theme_toggle_auto_button);
186             case FORCED_DAY_MODE -> getContext().getDrawable(R.drawable.ic_theme_toggle_day_button);
187             case FORCED_NIGHT_MODE -> getContext().getDrawable(
188                     R.drawable.ic_theme_toggle_night_button);
189             default -> null;
190         };
191     }
192 }
193