blob: 02d31ad1e6e00e35891af4c2b2018dcecb097dbe [file] [log] [blame]
Yao Chen3a7976d2016-01-20 17:27:08 -08001/*
2 * Copyright (C) 2015 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 */
16package com.android.car;
17
Yao Chen94d47662016-07-28 14:03:28 -070018import android.car.CarApiUtil;
19import android.car.settings.CarSettings;
20import android.car.settings.GarageModeSettingsObserver;
Yao Chen3a7976d2016-01-20 17:27:08 -080021import android.content.Context;
22import android.content.SharedPreferences;
Yao Chen94d47662016-07-28 14:03:28 -070023import android.net.Uri;
Yao Chen3a7976d2016-01-20 17:27:08 -080024import android.os.Handler;
25import android.os.IDeviceIdleController;
26import android.os.IMaintenanceActivityListener;
27import android.os.Message;
28import android.os.RemoteException;
29import android.os.ServiceManager;
Yao Chen94d47662016-07-28 14:03:28 -070030import android.preference.PreferenceManager;
31import android.provider.Settings;
Yao Chen3a7976d2016-01-20 17:27:08 -080032import android.util.Log;
33
34import com.android.internal.annotations.GuardedBy;
Yao Chendacd7242016-01-26 14:42:42 -080035import com.android.internal.annotations.VisibleForTesting;
Yao Chen3a7976d2016-01-20 17:27:08 -080036
37import java.io.PrintWriter;
38import java.util.Calendar;
39
Yao Chen94d47662016-07-28 14:03:28 -070040import static android.car.settings.GarageModeSettingsObserver.GARAGE_MODE_ENABLED_URI;
41import static android.car.settings.GarageModeSettingsObserver.GARAGE_MODE_MAINTENANCE_WINDOW_URI;
42import static android.car.settings.GarageModeSettingsObserver.GARAGE_MODE_WAKE_UP_TIME_URI;
43
Yao Chen3a7976d2016-01-20 17:27:08 -080044/**
45 * Controls car garage mode.
46 *
47 * Car garage mode is a time window for the car to do maintenance work when the car is not in use.
48 * The {@link com.android.car.GarageModeService} interacts with {@link com.android.car.CarPowerManagementService}
49 * to start and end garage mode. A {@link com.android.car.GarageModeService.GarageModePolicy} defines
50 * when the garage mode should start and how long it should last.
51 */
52public class GarageModeService implements CarServiceBase,
53 CarPowerManagementService.PowerEventProcessingHandler,
Yao Chendacd7242016-01-26 14:42:42 -080054 CarPowerManagementService.PowerServiceEventListener,
55 DeviceIdleControllerWrapper.DeviceMaintenanceActivityListener {
Yao Chen3a7976d2016-01-20 17:27:08 -080056 private static String TAG = "GarageModeService";
Vitalii Tomkiv1b1247b2016-09-30 11:27:19 -070057 private static final boolean DBG = false;
Yao Chen3a7976d2016-01-20 17:27:08 -080058
59 private static final int MSG_EXIT_GARAGE_MODE_EARLY = 0;
60 private static final int MSG_WRITE_TO_PREF = 1;
61
Yao Chen94d47662016-07-28 14:03:28 -070062 private static final String KEY_GARAGE_MODE_INDEX = "garage_mode_index";
63
Yao Chen3a7976d2016-01-20 17:27:08 -080064 // wait for 10 seconds to allow maintenance activities to start (e.g., connecting to wifi).
Yao Chendacd7242016-01-26 14:42:42 -080065 protected static final int MAINTENANCE_ACTIVITY_START_GRACE_PERIOUD = 10 * 1000;
Yao Chen3a7976d2016-01-20 17:27:08 -080066
67 private final CarPowerManagementService mPowerManagementService;
68 protected final Context mContext;
69
Yao Chendacd7242016-01-26 14:42:42 -080070 @VisibleForTesting
Yao Chen3a7976d2016-01-20 17:27:08 -080071 @GuardedBy("this")
Yao Chendacd7242016-01-26 14:42:42 -080072 protected boolean mInGarageMode;
73 @VisibleForTesting
Yao Chen3a7976d2016-01-20 17:27:08 -080074 @GuardedBy("this")
Yao Chendacd7242016-01-26 14:42:42 -080075 protected boolean mMaintenanceActive;
76 @VisibleForTesting
Yao Chen3a7976d2016-01-20 17:27:08 -080077 @GuardedBy("this")
Yao Chendacd7242016-01-26 14:42:42 -080078 protected int mGarageModeIndex;
Yao Chen3a7976d2016-01-20 17:27:08 -080079
Yao Chen94d47662016-07-28 14:03:28 -070080 @GuardedBy("this")
81 @VisibleForTesting
82 protected int mMaintenanceWindow;
83 @GuardedBy("this")
Yao Chen3a7976d2016-01-20 17:27:08 -080084 private GarageModePolicy mPolicy;
Yao Chen94d47662016-07-28 14:03:28 -070085 @GuardedBy("this")
86 private int mWakeUpHour = 0;
87 @GuardedBy("this")
88 private int mWakeUpMin = 0;
89 @GuardedBy("this")
90 private boolean mGarageModeEnabled;
91
Yao Chen3a7976d2016-01-20 17:27:08 -080092
93 private SharedPreferences mSharedPreferences;
Yao Chen94d47662016-07-28 14:03:28 -070094 private final GarageModeSettingsObserver mContentObserver;
Yao Chen3a7976d2016-01-20 17:27:08 -080095
Yao Chendacd7242016-01-26 14:42:42 -080096 private DeviceIdleControllerWrapper mDeviceIdleController;
Yao Chen94d47662016-07-28 14:03:28 -070097 private final GarageModeHandler mHandler = new GarageModeHandler();
Yao Chen3a7976d2016-01-20 17:27:08 -080098
Yao Chen3a7976d2016-01-20 17:27:08 -080099 private class GarageModeHandler extends Handler {
100 @Override
101 public void handleMessage(Message msg) {
102 switch (msg.what) {
103 case MSG_EXIT_GARAGE_MODE_EARLY:
104 mPowerManagementService.notifyPowerEventProcessingCompletion(
105 GarageModeService.this);
106 break;
107 case MSG_WRITE_TO_PREF:
108 writeToPref(msg.arg1);
109 break;
110 }
111 }
112 }
113
114 public GarageModeService(Context context, CarPowerManagementService powerManagementService) {
Yao Chendacd7242016-01-26 14:42:42 -0800115 this(context, powerManagementService, null);
116 }
117
118 @VisibleForTesting
119 protected GarageModeService(Context context, CarPowerManagementService powerManagementService,
120 DeviceIdleControllerWrapper deviceIdleController) {
Yao Chen3a7976d2016-01-20 17:27:08 -0800121 mContext = context;
122 mPowerManagementService = powerManagementService;
Yao Chendacd7242016-01-26 14:42:42 -0800123 if (deviceIdleController == null) {
124 mDeviceIdleController = new DefaultDeviceIdleController();
125 } else {
126 mDeviceIdleController = deviceIdleController;
127 }
Yao Chen94d47662016-07-28 14:03:28 -0700128 mContentObserver = new GarageModeSettingsObserver(mContext, mHandler) {
129 @Override
130 public void onChange(boolean selfChange, Uri uri) {
131 onSettingsChangedInternal(uri);
132 }
133 };
Yao Chen3a7976d2016-01-20 17:27:08 -0800134 }
135
136 @Override
137 public void init() {
Yao Chen94d47662016-07-28 14:03:28 -0700138 logd("init GarageMode");
Yao Chen96fd18b2017-03-22 13:16:31 -0700139 mSharedPreferences = PreferenceManager.getDefaultSharedPreferences(
140 mContext.createDeviceProtectedStorageContext());
Yao Chen94d47662016-07-28 14:03:28 -0700141 final int index = mSharedPreferences.getInt(KEY_GARAGE_MODE_INDEX, 0);
Yao Chen3a7976d2016-01-20 17:27:08 -0800142 synchronized (this) {
Yao Chendacd7242016-01-26 14:42:42 -0800143 mMaintenanceActive = mDeviceIdleController.startTracking(this);
144 mGarageModeIndex = index;
Yao Chen94d47662016-07-28 14:03:28 -0700145 readPolicyLocked();
146 readFromSettingsLocked(CarSettings.Global.KEY_GARAGE_MODE_MAINTENANCE_WINDOW,
147 CarSettings.Global.KEY_GARAGE_MODE_ENABLED,
148 CarSettings.Global.KEY_GARAGE_MODE_WAKE_UP_TIME);
Yao Chen3a7976d2016-01-20 17:27:08 -0800149 }
Yao Chen94d47662016-07-28 14:03:28 -0700150 mContentObserver.register();
Yao Chen3a7976d2016-01-20 17:27:08 -0800151 mPowerManagementService.registerPowerEventProcessingHandler(this);
152 }
153
154 @Override
155 public void release() {
Yao Chen94d47662016-07-28 14:03:28 -0700156 logd("release GarageModeService");
Yao Chendacd7242016-01-26 14:42:42 -0800157 mDeviceIdleController.stopTracking();
Yao Chen94d47662016-07-28 14:03:28 -0700158 mContentObserver.unregister();
Yao Chen3a7976d2016-01-20 17:27:08 -0800159 }
160
161 @Override
162 public void dump(PrintWriter writer) {
163 writer.println("mGarageModeIndex: " + mGarageModeIndex);
164 writer.println("inGarageMode? " + mInGarageMode);
Yao Chen94d47662016-07-28 14:03:28 -0700165 writer.println("GarageModeTime: " + mWakeUpHour + ":" + mWakeUpMin);
166 writer.println("GarageModeEnabled " + mGarageModeEnabled);
167 writer.println("GarageModeTimeWindow " + mMaintenanceWindow + " ms");
Yao Chen3a7976d2016-01-20 17:27:08 -0800168 }
169
170 @Override
171 public long onPrepareShutdown(boolean shuttingDown) {
172 // this is the beginning of each garage mode.
173 synchronized (this) {
Yao Chen94d47662016-07-28 14:03:28 -0700174 logd("onPrePowerEvent " + shuttingDown);
Yao Chen3a7976d2016-01-20 17:27:08 -0800175 mInGarageMode = true;
176 mGarageModeIndex++;
177 mHandler.removeMessages(MSG_EXIT_GARAGE_MODE_EARLY);
178 if (!mMaintenanceActive) {
179 mHandler.sendMessageDelayed(
180 mHandler.obtainMessage(MSG_EXIT_GARAGE_MODE_EARLY),
181 MAINTENANCE_ACTIVITY_START_GRACE_PERIOUD);
182 }
183 // We always reserve the maintenance window first. If later, we found no
184 // maintenance work active, we will exit garage mode early after
185 // MAINTENANCE_ACTIVITY_START_GRACE_PERIOUD
Yao Chen94d47662016-07-28 14:03:28 -0700186 return mMaintenanceWindow;
Yao Chen3a7976d2016-01-20 17:27:08 -0800187 }
188 }
189
190 @Override
191 public void onPowerOn(boolean displayOn) {
192 synchronized (this) {
Yao Chen94d47662016-07-28 14:03:28 -0700193 logd("onPowerOn: " + displayOn);
Yao Chen3a7976d2016-01-20 17:27:08 -0800194 if (displayOn) {
195 // the car is use now. reset the garage mode counter.
196 mGarageModeIndex = 0;
197 }
198 }
199 }
200
201 @Override
202 public int getWakeupTime() {
Yao Chen3a7976d2016-01-20 17:27:08 -0800203 synchronized (this) {
Yao Chen94d47662016-07-28 14:03:28 -0700204 if (!mGarageModeEnabled) {
205 return 0;
206 }
207 return mPolicy.getNextWakeUpTime(mGarageModeIndex, mWakeUpHour, mWakeUpMin);
Yao Chen3a7976d2016-01-20 17:27:08 -0800208 }
209 }
210
211 @Override
212 public void onSleepExit() {
213 // ignored
214 }
215
216 @Override
217 public void onSleepEntry() {
218 synchronized (this) {
219 mInGarageMode = false;
220 }
221 }
222
223 @Override
224 public void onShutdown() {
225 synchronized (this) {
226 mHandler.sendMessage(
227 mHandler.obtainMessage(MSG_WRITE_TO_PREF, mGarageModeIndex, 0));
228 }
229 }
230
231 private void readPolicyLocked() {
Yao Chen94d47662016-07-28 14:03:28 -0700232 logd("readPolicy");
Keun-young Parkf9215202016-10-10 12:34:08 -0700233 // TODO: define a xml schema for policy and read it from system dir. bug: 32096969
Yao Chen3a7976d2016-01-20 17:27:08 -0800234 mPolicy = new DefaultGarageModePolicy();
235 }
236
237 private void writeToPref(int index) {
238 SharedPreferences.Editor editor = mSharedPreferences.edit();
Yao Chen94d47662016-07-28 14:03:28 -0700239 editor.putInt(KEY_GARAGE_MODE_INDEX, index);
Yao Chen3a7976d2016-01-20 17:27:08 -0800240 editor.commit();
241 }
242
Yao Chendacd7242016-01-26 14:42:42 -0800243 @Override
244 public void onMaintenanceActivityChanged(boolean active) {
Yao Chen3a7976d2016-01-20 17:27:08 -0800245 boolean shouldReportCompletion = false;
246 synchronized (this) {
Yao Chen94d47662016-07-28 14:03:28 -0700247 logd("onMaintenanceActivityChanged: " + active);
Yao Chendacd7242016-01-26 14:42:42 -0800248 mMaintenanceActive = active;
249 if (!mInGarageMode) {
Yao Chen3a7976d2016-01-20 17:27:08 -0800250 return;
251 }
Yao Chen3a7976d2016-01-20 17:27:08 -0800252
253 if (!active) {
254 shouldReportCompletion = true;
255 mInGarageMode = false;
256 } else {
257 // we are in garage mode, and maintenance work has just begun.
258 mHandler.removeMessages(MSG_EXIT_GARAGE_MODE_EARLY);
259 }
260 }
261 if (shouldReportCompletion) {
262 // we are in garage mode, and maintenance work has finished.
263 mPowerManagementService.notifyPowerEventProcessingCompletion(this);
264 }
265 }
266
Yao Chen3a7976d2016-01-20 17:27:08 -0800267 public abstract static class GarageModePolicy {
Yao Chen94d47662016-07-28 14:03:28 -0700268 abstract public int getNextWakeUpTime(int index, int hour, int min);
Yao Chen3a7976d2016-01-20 17:27:08 -0800269 /**
270 * Returns number of seconds between now to 1am {@param numDays} days later.
271 */
Yao Chen94d47662016-07-28 14:03:28 -0700272 public static int nextWakeUpSeconds(int numDays, int hour, int min) {
Keun-young Parkf9215202016-10-10 12:34:08 -0700273 // TODO: Should select a random time within a window. bug: 32096386
274 // This is to avoid all cars update at the same time.
Yao Chen3a7976d2016-01-20 17:27:08 -0800275 Calendar next = Calendar.getInstance();
276 next.add(Calendar.DATE, numDays);
Yao Chen94d47662016-07-28 14:03:28 -0700277 next.set(Calendar.HOUR_OF_DAY, hour);
278 next.set(Calendar.MINUTE, min);
Yao Chen3a7976d2016-01-20 17:27:08 -0800279 next.set(Calendar.SECOND, 0);
280
281 Calendar now = Calendar.getInstance();
282 return (next.get(Calendar.MILLISECOND) - now.get(Calendar.MILLISECOND)) / 1000;
283 }
284 }
285
286 /**
287 * Default garage mode policy.
288 *
289 * The first wake up time is set to be 1am the next day. And it keeps waking up every day for a
290 * week. After that, wake up every 7 days for a month, and wake up every 30 days thereafter.
291 */
292 private static class DefaultGarageModePolicy extends GarageModePolicy {
293 private static final int COL_INDEX = 0;
294 private static final int COL_WAKEUP_TIME = 1;
295
296 private static final int[][] WAKE_UP_TIME = new int[][] {
Yao Chen94d47662016-07-28 14:03:28 -0700297 {7 /*index <= 7*/, 1 /* wake up the next day */},
298 {11 /* 7 < index <= 11 */, 7 /* wake up the next week */},
299 {Integer.MAX_VALUE /* index > 11 */, 30 /* wake up the next month */}
Yao Chen3a7976d2016-01-20 17:27:08 -0800300 };
301
302 @Override
Yao Chen94d47662016-07-28 14:03:28 -0700303 public int getNextWakeUpTime(int index, int hour, int min) {
Yao Chen3a7976d2016-01-20 17:27:08 -0800304 for (int i = 0; i < WAKE_UP_TIME.length; i++) {
305 if (index <= WAKE_UP_TIME[i][COL_INDEX]) {
Yao Chen94d47662016-07-28 14:03:28 -0700306 return nextWakeUpSeconds(WAKE_UP_TIME[i][COL_WAKEUP_TIME], hour, min);
Yao Chen3a7976d2016-01-20 17:27:08 -0800307 }
308 }
309
310 Log.w(TAG, "Integer.MAX number of wake ups... How long have we been sleeping? ");
311 return 0;
312 }
313 }
Yao Chendacd7242016-01-26 14:42:42 -0800314
315 private static class DefaultDeviceIdleController extends DeviceIdleControllerWrapper {
316 private IDeviceIdleController mDeviceIdleController;
317 private MaintenanceActivityListener mMaintenanceActivityListener
318 = new MaintenanceActivityListener();
319
320 @Override
321 public boolean startLocked() {
322 mDeviceIdleController = IDeviceIdleController.Stub.asInterface(
323 ServiceManager.getService(Context.DEVICE_IDLE_CONTROLLER));
324 boolean active = false;
325 try {
326 active = mDeviceIdleController
327 .registerMaintenanceActivityListener(mMaintenanceActivityListener);
328 } catch (RemoteException e) {
329 Log.e(TAG, "Unable to register listener with DeviceIdleController", e);
330 }
331 return active;
332 }
333
334 @Override
335 public void stopTracking() {
336 try {
337 if (mDeviceIdleController != null) {
338 mDeviceIdleController.unregisterMaintenanceActivityListener(
339 mMaintenanceActivityListener);
340 }
341 } catch (RemoteException e) {
342 Log.e(TAG, "Fail to unregister listener.", e);
343 }
344 }
345
346 private final class MaintenanceActivityListener extends IMaintenanceActivityListener.Stub {
347 @Override
348 public void onMaintenanceActivityChanged(final boolean active) {
349 DefaultDeviceIdleController.this.setMaintenanceActivity(active);
350 }
351 }
352 }
Yao Chen94d47662016-07-28 14:03:28 -0700353
354 private void logd(String msg) {
355 if (DBG) {
356 Log.d(TAG, msg);
357 }
358 }
359
360 private void readFromSettingsLocked(String... keys) {
361 for (String key : keys) {
362 switch (key) {
363 case CarSettings.Global.KEY_GARAGE_MODE_ENABLED:
364 mGarageModeEnabled =
365 Settings.Global.getInt(mContext.getContentResolver(), key, 1) == 1;
366 break;
367 case CarSettings.Global.KEY_GARAGE_MODE_WAKE_UP_TIME:
368 int time[] = CarApiUtil.decodeGarageTimeSetting(
369 Settings.Global.getString(mContext.getContentResolver(),
370 CarSettings.Global.KEY_GARAGE_MODE_WAKE_UP_TIME));
371 mWakeUpHour = time[0];
372 mWakeUpMin = time[1];
373 break;
374 case CarSettings.Global.KEY_GARAGE_MODE_MAINTENANCE_WINDOW:
375 mMaintenanceWindow = Settings.Global.getInt(
376 mContext.getContentResolver(), key,
377 CarSettings.DEFAULT_GARAGE_MODE_MAINTENANCE_WINDOW);
378 break;
379 default:
380 Log.e(TAG, "Unknown setting key " + key);
381 }
382 }
383 }
384
385 private void onSettingsChangedInternal(Uri uri) {
386 synchronized (this) {
387 logd("Content Observer onChange: " + uri);
388 if (uri.equals(GARAGE_MODE_ENABLED_URI)) {
389 readFromSettingsLocked(CarSettings.Global.KEY_GARAGE_MODE_ENABLED);
390 } else if (uri.equals(GARAGE_MODE_WAKE_UP_TIME_URI)) {
391 readFromSettingsLocked(CarSettings.Global.KEY_GARAGE_MODE_WAKE_UP_TIME);
392 } else if (uri.equals(GARAGE_MODE_MAINTENANCE_WINDOW_URI)) {
393 readFromSettingsLocked(CarSettings.Global.KEY_GARAGE_MODE_MAINTENANCE_WINDOW);
394 }
395 logd(String.format(
396 "onSettingsChanged %s. enabled: %s, wakeUpTime: %d:%d, windowSize: %d",
397 uri, mGarageModeEnabled, mWakeUpHour, mWakeUpMin, mMaintenanceWindow));
398 }
399 }
Yao Chen3a7976d2016-01-20 17:27:08 -0800400}