1 /*
2  * Copyright (C) 2016 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;
18 
19 import static android.car.settings.CarSettings.Global.FORCED_DAY_NIGHT_MODE;
20 
21 import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO;
22 
23 import android.annotation.IntDef;
24 import android.app.UiModeManager;
25 import android.car.builtin.util.Slogf;
26 import android.car.feature.Flags;
27 import android.car.hardware.CarPropertyValue;
28 import android.car.hardware.property.CarPropertyEvent;
29 import android.car.hardware.property.ICarPropertyEventListener;
30 import android.content.Context;
31 import android.database.ContentObserver;
32 import android.hardware.automotive.vehicle.VehicleProperty;
33 import android.os.RemoteException;
34 import android.provider.Settings;
35 import android.util.Log;
36 import android.util.proto.ProtoOutputStream;
37 
38 import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
39 import com.android.car.internal.util.IndentingPrintWriter;
40 import com.android.internal.annotations.GuardedBy;
41 
42 import java.lang.annotation.Retention;
43 import java.lang.annotation.RetentionPolicy;
44 import java.util.List;
45 
46 /**
47  * Class used to handle events used to set vehicle in night mode.
48  */
49 public class CarNightService implements CarServiceBase {
50 
51     public static final boolean DBG = Slogf.isLoggable(CarLog.TAG_SENSOR, Log.DEBUG);
52 
53     @IntDef({FORCED_SENSOR_MODE, FORCED_DAY_MODE, FORCED_NIGHT_MODE})
54     @Retention(RetentionPolicy.SOURCE)
55     public @interface DayNightSensorMode {}
56 
57     public static final int FORCED_SENSOR_MODE = 0;
58     public static final int FORCED_DAY_MODE = 1;
59     public static final int FORCED_NIGHT_MODE = 2;
60 
61     private final Object mLock = new Object();
62     @GuardedBy("mLock")
63     private int mNightSetting = UiModeManager.MODE_NIGHT_YES;
64     @GuardedBy("mLock")
65     private int mForcedMode = FORCED_SENSOR_MODE;
66     @GuardedBy("mLock")
67     private long mLastSensorEventTime = -1;
68     private final Context mContext;
69     @GuardedBy("mLock")
70     private final UiModeManager mUiModeManager;
71     private final CarPropertyService mCarPropertyService;
72 
73     private final ICarPropertyEventListener mICarPropertyEventListener =
74             new ICarPropertyEventListener.Stub() {
75                 @Override
76                 public void onEvent(List<CarPropertyEvent> events) throws RemoteException {
77                     synchronized (mLock) {
78                         for (CarPropertyEvent event : events) {
79                             onNightModeCarPropertyEventLocked(event);
80                         }
81                     }
82                 }
83             };
84 
85     private final ContentObserver mForcedNightModeObserver =
86             new ContentObserver(null /* handler */) {
87                 @Override
88                 public void onChange(boolean selfChange) {
89                     synchronized (mLock) {
90                         forceDayNightModeInternal(getForcedDayNightModeSetting());
91                     }
92                 }
93             };
94 
95     /**
96      * Acts on {@link CarPropertyEvent} events marked with {@link
97      * CarPropertyEvent.PROPERTY_EVENT_PROPERTY_CHANGE} and marked with {@link
98      * VehicleProperty.NIGHT_MODE} by setting the vehicle in night mode.
99      * <p>
100      * This method does nothing if the event parameter is {@code null}.
101      *
102      * @param event the car property event to be handled
103      */
104     @GuardedBy("mLock")
onNightModeCarPropertyEventLocked(CarPropertyEvent event)105     private void onNightModeCarPropertyEventLocked(CarPropertyEvent event) {
106         if (event == null) {
107             return;
108         }
109         if (event.getEventType() == CarPropertyEvent.PROPERTY_EVENT_PROPERTY_CHANGE) {
110             // Only handle onChange events
111             CarPropertyValue value = event.getCarPropertyValue();
112             if (value.getPropertyId() == VehicleProperty.NIGHT_MODE
113                     && value.getTimestamp() > mLastSensorEventTime) {
114                 mLastSensorEventTime = value.getTimestamp();
115                 boolean nightMode = (Boolean) value.getValue();
116                 Slogf.i(CarLog.TAG_SENSOR, "Set dayNight Mode as "
117                         + nightMode + " at timestamp: " + mLastSensorEventTime);
118                 setNightModeLocked(nightMode);
119             }
120         }
121     }
122 
123     @GuardedBy("mLock")
setNightModeLocked(boolean nightMode)124     private void setNightModeLocked(boolean nightMode) {
125         if (nightMode) {
126             mNightSetting = UiModeManager.MODE_NIGHT_YES;
127             if (DBG) Slogf.d(CarLog.TAG_SENSOR, "CAR dayNight handleSensorEvent NIGHT");
128         } else {
129             mNightSetting = UiModeManager.MODE_NIGHT_NO;
130             if (DBG) Slogf.d(CarLog.TAG_SENSOR, "CAR dayNight handleSensorEvent DAY");
131         }
132         if (mUiModeManager != null && (mForcedMode == FORCED_SENSOR_MODE)) {
133             mUiModeManager.setNightMode(mNightSetting);
134             if (DBG) Slogf.d(CarLog.TAG_SENSOR, "CAR dayNight handleSensorEvent APPLIED");
135         } else {
136             if (DBG) Slogf.d(CarLog.TAG_SENSOR, "CAR dayNight handleSensorEvent IGNORED");
137         }
138     }
139 
140     @DayNightSensorMode
getForcedDayNightModeSetting()141     private int getForcedDayNightModeSetting() {
142         return Settings.Global.getInt(mContext.getContentResolver(),
143                 FORCED_DAY_NIGHT_MODE, FORCED_SENSOR_MODE);
144     }
145 
146     /**
147      * Sets {@link UiModeManager} to night mode according to the {@link DayNightSensorMode} passed
148      * as parameter.
149      *
150      * @param mode the sensor mode used to set vehicle in night mode
151      * @return the current night mode, or {@code -1} on error
152      */
forceDayNightMode(@ayNightSensorMode int mode)153     public int forceDayNightMode(@DayNightSensorMode int mode) {
154         synchronized (mLock) {
155             return forceDayNightModeInternal(mode);
156         }
157     }
158 
159     @GuardedBy("mLock")
forceDayNightModeInternal(@ayNightSensorMode int mode)160     private int forceDayNightModeInternal(@DayNightSensorMode int mode) {
161         if (mUiModeManager == null) {
162             return -1;
163         }
164         int resultMode;
165         switch (mode) {
166             case FORCED_SENSOR_MODE:
167                 resultMode = mNightSetting;
168                 mForcedMode = FORCED_SENSOR_MODE;
169                 break;
170             case FORCED_DAY_MODE:
171                 resultMode = UiModeManager.MODE_NIGHT_NO;
172                 mForcedMode = FORCED_DAY_MODE;
173                 break;
174             case FORCED_NIGHT_MODE:
175                 resultMode = UiModeManager.MODE_NIGHT_YES;
176                 mForcedMode = FORCED_NIGHT_MODE;
177                 break;
178             default:
179                 Slogf.e(CarLog.TAG_SENSOR, "Unknown forced day/night mode " + mode);
180                 return -1;
181         }
182         mUiModeManager.setNightMode(resultMode);
183         return mUiModeManager.getNightMode();
184     }
185 
CarNightService(Context context, CarPropertyService propertyService)186     CarNightService(Context context, CarPropertyService propertyService) {
187         mContext = context;
188         mCarPropertyService = propertyService;
189         mUiModeManager = (UiModeManager) mContext.getSystemService(Context.UI_MODE_SERVICE);
190         if (mUiModeManager == null) {
191             Slogf.w(CarLog.TAG_SENSOR, "Failed to get UI_MODE_SERVICE");
192         }
193     }
194 
195     @Override
init()196     public void init() {
197         if (DBG) {
198             Slogf.d(CarLog.TAG_SENSOR, "CAR dayNight init.");
199         }
200         synchronized (mLock) {
201             if (Flags.carNightGlobalSetting()) {
202                 mContext.getContentResolver().registerContentObserver(
203                         Settings.Global.getUriFor(FORCED_DAY_NIGHT_MODE),
204                         false /* notifyForDescendants */,
205                         mForcedNightModeObserver);
206                 mForcedMode = getForcedDayNightModeSetting();
207                 if (mForcedMode != FORCED_SENSOR_MODE) {
208                     // Only update the mode if it's not currently sensor mode. If it is sensor mode,
209                     // it will be updated by the setNightModeLocked call below.
210                     forceDayNightModeInternal(mForcedMode);
211                 }
212             }
213 
214             mCarPropertyService.registerListenerSafe(VehicleProperty.NIGHT_MODE, 0,
215                     mICarPropertyEventListener);
216             CarPropertyValue propertyValue = mCarPropertyService.getPropertySafe(
217                     VehicleProperty.NIGHT_MODE, 0);
218             if (propertyValue != null && propertyValue.getTimestamp() != 0) {
219                 mLastSensorEventTime = propertyValue.getTimestamp();
220                 setNightModeLocked((Boolean) propertyValue.getValue());
221             } else {
222                 Slogf.w(CarLog.TAG_SENSOR, "Failed to get value of NIGHT_MODE");
223                 setNightModeLocked(true);
224             }
225         }
226     }
227 
228     @Override
release()229     public void release() {
230     }
231 
232     @Override
233     @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
dump(IndentingPrintWriter writer)234     public void dump(IndentingPrintWriter writer) {
235         synchronized (mLock) {
236             writer.println("*DAY NIGHT POLICY*");
237             writer.println(
238                     "Mode:" + ((mNightSetting == UiModeManager.MODE_NIGHT_YES) ? "night" : "day"));
239             writer.println("Forced Mode? " + (mForcedMode == FORCED_SENSOR_MODE
240                     ? "false, timestamp of dayNight sensor is: " + mLastSensorEventTime
241                     : (mForcedMode == FORCED_DAY_MODE ? "day" : "night")));
242         }
243     }
244 
245     @Override
246     @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
dumpProto(ProtoOutputStream proto)247     public void dumpProto(ProtoOutputStream proto) {}
248 }
249