blob: 801b3003e7532761b3ac2bef29d2b6f92f6c9838 [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
18import android.content.Context;
19import android.content.SharedPreferences;
20import android.os.Handler;
21import android.os.IDeviceIdleController;
22import android.os.IMaintenanceActivityListener;
23import android.os.Message;
24import android.os.RemoteException;
25import android.os.ServiceManager;
26import android.util.Log;
27
28import com.android.internal.annotations.GuardedBy;
Yao Chendacd7242016-01-26 14:42:42 -080029import com.android.internal.annotations.VisibleForTesting;
Yao Chen3a7976d2016-01-20 17:27:08 -080030
31import java.io.PrintWriter;
32import java.util.Calendar;
33
34/**
35 * Controls car garage mode.
36 *
37 * Car garage mode is a time window for the car to do maintenance work when the car is not in use.
38 * The {@link com.android.car.GarageModeService} interacts with {@link com.android.car.CarPowerManagementService}
39 * to start and end garage mode. A {@link com.android.car.GarageModeService.GarageModePolicy} defines
40 * when the garage mode should start and how long it should last.
41 */
42public class GarageModeService implements CarServiceBase,
43 CarPowerManagementService.PowerEventProcessingHandler,
Yao Chendacd7242016-01-26 14:42:42 -080044 CarPowerManagementService.PowerServiceEventListener,
45 DeviceIdleControllerWrapper.DeviceMaintenanceActivityListener {
Yao Chen3a7976d2016-01-20 17:27:08 -080046 private static String TAG = "GarageModeService";
47 private static String GARAGE_MODE_PREFERENCE_FILE = "com.android.car.PREFERENCE_FILE_KEY";
48 private static String GARAGE_MODE_INDEX = "garage_mode_index";
49
50 private static final int MSG_EXIT_GARAGE_MODE_EARLY = 0;
51 private static final int MSG_WRITE_TO_PREF = 1;
52
53 // TODO: move this to garage mode policy too.
Yao Chendacd7242016-01-26 14:42:42 -080054 @VisibleForTesting
55 protected static final int MAINTENANCE_WINDOW = 5 * 60 * 1000; // 5 minutes
Yao Chen3a7976d2016-01-20 17:27:08 -080056 // wait for 10 seconds to allow maintenance activities to start (e.g., connecting to wifi).
Yao Chendacd7242016-01-26 14:42:42 -080057 protected static final int MAINTENANCE_ACTIVITY_START_GRACE_PERIOUD = 10 * 1000;
Yao Chen3a7976d2016-01-20 17:27:08 -080058
59 private final CarPowerManagementService mPowerManagementService;
60 protected final Context mContext;
61
Yao Chendacd7242016-01-26 14:42:42 -080062 @VisibleForTesting
Yao Chen3a7976d2016-01-20 17:27:08 -080063 @GuardedBy("this")
Yao Chendacd7242016-01-26 14:42:42 -080064 protected boolean mInGarageMode;
65 @VisibleForTesting
Yao Chen3a7976d2016-01-20 17:27:08 -080066 @GuardedBy("this")
Yao Chendacd7242016-01-26 14:42:42 -080067 protected boolean mMaintenanceActive;
68 @VisibleForTesting
Yao Chen3a7976d2016-01-20 17:27:08 -080069 @GuardedBy("this")
Yao Chendacd7242016-01-26 14:42:42 -080070 protected int mGarageModeIndex;
Yao Chen3a7976d2016-01-20 17:27:08 -080071
72 private final Object mPolicyLock = new Object();
73 @GuardedBy("mPolicyLock")
74 private GarageModePolicy mPolicy;
75
76 private SharedPreferences mSharedPreferences;
77
Yao Chendacd7242016-01-26 14:42:42 -080078 private DeviceIdleControllerWrapper mDeviceIdleController;
Yao Chen3a7976d2016-01-20 17:27:08 -080079 private GarageModeHandler mHandler = new GarageModeHandler();
80
Yao Chen3a7976d2016-01-20 17:27:08 -080081 private class GarageModeHandler extends Handler {
82 @Override
83 public void handleMessage(Message msg) {
84 switch (msg.what) {
85 case MSG_EXIT_GARAGE_MODE_EARLY:
86 mPowerManagementService.notifyPowerEventProcessingCompletion(
87 GarageModeService.this);
88 break;
89 case MSG_WRITE_TO_PREF:
90 writeToPref(msg.arg1);
91 break;
92 }
93 }
94 }
95
96 public GarageModeService(Context context, CarPowerManagementService powerManagementService) {
Yao Chendacd7242016-01-26 14:42:42 -080097 this(context, powerManagementService, null);
98 }
99
100 @VisibleForTesting
101 protected GarageModeService(Context context, CarPowerManagementService powerManagementService,
102 DeviceIdleControllerWrapper deviceIdleController) {
Yao Chen3a7976d2016-01-20 17:27:08 -0800103 mContext = context;
104 mPowerManagementService = powerManagementService;
Yao Chendacd7242016-01-26 14:42:42 -0800105 if (deviceIdleController == null) {
106 mDeviceIdleController = new DefaultDeviceIdleController();
107 } else {
108 mDeviceIdleController = deviceIdleController;
109 }
Yao Chen3a7976d2016-01-20 17:27:08 -0800110 }
111
112 @Override
113 public void init() {
114 Log.d(TAG, "init GarageMode");
115 mSharedPreferences = mContext.getSharedPreferences(
116 GARAGE_MODE_PREFERENCE_FILE, Context.MODE_PRIVATE);
117 synchronized (mPolicyLock) {
118 readPolicyLocked();
119 }
120
Yao Chen3a7976d2016-01-20 17:27:08 -0800121 final int index = mSharedPreferences.getInt(GARAGE_MODE_INDEX, 0);
Yao Chen3a7976d2016-01-20 17:27:08 -0800122 synchronized (this) {
Yao Chendacd7242016-01-26 14:42:42 -0800123 mMaintenanceActive = mDeviceIdleController.startTracking(this);
124 mGarageModeIndex = index;
Yao Chen3a7976d2016-01-20 17:27:08 -0800125 }
126 mPowerManagementService.registerPowerEventProcessingHandler(this);
127 }
128
129 @Override
130 public void release() {
131 Log.d(TAG, "release GarageModeService");
Yao Chendacd7242016-01-26 14:42:42 -0800132 mDeviceIdleController.stopTracking();
Yao Chen3a7976d2016-01-20 17:27:08 -0800133 }
134
135 @Override
136 public void dump(PrintWriter writer) {
137 writer.println("mGarageModeIndex: " + mGarageModeIndex);
138 writer.println("inGarageMode? " + mInGarageMode);
139 }
140
141 @Override
142 public long onPrepareShutdown(boolean shuttingDown) {
143 // this is the beginning of each garage mode.
144 synchronized (this) {
145 Log.d(TAG, "onPrePowerEvent " + shuttingDown);
146 mInGarageMode = true;
147 mGarageModeIndex++;
148 mHandler.removeMessages(MSG_EXIT_GARAGE_MODE_EARLY);
149 if (!mMaintenanceActive) {
150 mHandler.sendMessageDelayed(
151 mHandler.obtainMessage(MSG_EXIT_GARAGE_MODE_EARLY),
152 MAINTENANCE_ACTIVITY_START_GRACE_PERIOUD);
153 }
154 // We always reserve the maintenance window first. If later, we found no
155 // maintenance work active, we will exit garage mode early after
156 // MAINTENANCE_ACTIVITY_START_GRACE_PERIOUD
157 return MAINTENANCE_WINDOW;
158 }
159 }
160
161 @Override
162 public void onPowerOn(boolean displayOn) {
163 synchronized (this) {
164 Log.d(TAG, "onPowerOn: " + displayOn);
165 if (displayOn) {
166 // the car is use now. reset the garage mode counter.
167 mGarageModeIndex = 0;
168 }
169 }
170 }
171
172 @Override
173 public int getWakeupTime() {
174 final int index;
175 synchronized (this) {
176 index = mGarageModeIndex;
177 }
178 synchronized (mPolicyLock) {
179 return mPolicy.getNextWakeUpTime(index);
180 }
181 }
182
183 @Override
184 public void onSleepExit() {
185 // ignored
186 }
187
188 @Override
189 public void onSleepEntry() {
190 synchronized (this) {
191 mInGarageMode = false;
192 }
193 }
194
195 @Override
196 public void onShutdown() {
197 synchronized (this) {
198 mHandler.sendMessage(
199 mHandler.obtainMessage(MSG_WRITE_TO_PREF, mGarageModeIndex, 0));
200 }
201 }
202
203 private void readPolicyLocked() {
204 Log.d(TAG, "readPolicyLocked");
205 // TODO: define a xml schema for garage mode policy and read it from system dir.
206 mPolicy = new DefaultGarageModePolicy();
207 }
208
209 private void writeToPref(int index) {
210 SharedPreferences.Editor editor = mSharedPreferences.edit();
211 editor.putInt(GARAGE_MODE_INDEX, index);
212 editor.commit();
213 }
214
Yao Chendacd7242016-01-26 14:42:42 -0800215 @Override
216 public void onMaintenanceActivityChanged(boolean active) {
Yao Chen3a7976d2016-01-20 17:27:08 -0800217 boolean shouldReportCompletion = false;
218 synchronized (this) {
219 Log.d(TAG, "onMaintenanceActivityChanged: " + active);
Yao Chendacd7242016-01-26 14:42:42 -0800220 mMaintenanceActive = active;
221 if (!mInGarageMode) {
Yao Chen3a7976d2016-01-20 17:27:08 -0800222 return;
223 }
Yao Chen3a7976d2016-01-20 17:27:08 -0800224
225 if (!active) {
226 shouldReportCompletion = true;
227 mInGarageMode = false;
228 } else {
229 // we are in garage mode, and maintenance work has just begun.
230 mHandler.removeMessages(MSG_EXIT_GARAGE_MODE_EARLY);
231 }
232 }
233 if (shouldReportCompletion) {
234 // we are in garage mode, and maintenance work has finished.
235 mPowerManagementService.notifyPowerEventProcessingCompletion(this);
236 }
237 }
238
Yao Chen3a7976d2016-01-20 17:27:08 -0800239 public abstract static class GarageModePolicy {
240 abstract public int getNextWakeUpTime(int index);
241 /**
242 * Returns number of seconds between now to 1am {@param numDays} days later.
243 */
244 public static int nextWakeUpSeconds(int numDays) {
245 // TODO: Should select a random time within a window to avoid all cars update at the
246 // same time.
247 Calendar next = Calendar.getInstance();
248 next.add(Calendar.DATE, numDays);
249 next.set(Calendar.HOUR_OF_DAY, 1);
250 next.set(Calendar.MINUTE, 0);
251 next.set(Calendar.SECOND, 0);
252
253 Calendar now = Calendar.getInstance();
254 return (next.get(Calendar.MILLISECOND) - now.get(Calendar.MILLISECOND)) / 1000;
255 }
256 }
257
258 /**
259 * Default garage mode policy.
260 *
261 * The first wake up time is set to be 1am the next day. And it keeps waking up every day for a
262 * week. After that, wake up every 7 days for a month, and wake up every 30 days thereafter.
263 */
264 private static class DefaultGarageModePolicy extends GarageModePolicy {
265 private static final int COL_INDEX = 0;
266 private static final int COL_WAKEUP_TIME = 1;
267
268 private static final int[][] WAKE_UP_TIME = new int[][] {
269 {7 /*index <= 7*/, 1 /* wake up the next day */},
270 {11 /* 7 < index <= 11 */, 7 /* wake up the next week */},
271 {Integer.MAX_VALUE /* index > 11 */, 30 /* wake up the next month */}
272 };
273
274 @Override
275 public int getNextWakeUpTime(int index) {
276 for (int i = 0; i < WAKE_UP_TIME.length; i++) {
277 if (index <= WAKE_UP_TIME[i][COL_INDEX]) {
278 return nextWakeUpSeconds(WAKE_UP_TIME[i][COL_WAKEUP_TIME]);
279 }
280 }
281
282 Log.w(TAG, "Integer.MAX number of wake ups... How long have we been sleeping? ");
283 return 0;
284 }
285 }
Yao Chendacd7242016-01-26 14:42:42 -0800286
287 private static class DefaultDeviceIdleController extends DeviceIdleControllerWrapper {
288 private IDeviceIdleController mDeviceIdleController;
289 private MaintenanceActivityListener mMaintenanceActivityListener
290 = new MaintenanceActivityListener();
291
292 @Override
293 public boolean startLocked() {
294 mDeviceIdleController = IDeviceIdleController.Stub.asInterface(
295 ServiceManager.getService(Context.DEVICE_IDLE_CONTROLLER));
296 boolean active = false;
297 try {
298 active = mDeviceIdleController
299 .registerMaintenanceActivityListener(mMaintenanceActivityListener);
300 } catch (RemoteException e) {
301 Log.e(TAG, "Unable to register listener with DeviceIdleController", e);
302 }
303 return active;
304 }
305
306 @Override
307 public void stopTracking() {
308 try {
309 if (mDeviceIdleController != null) {
310 mDeviceIdleController.unregisterMaintenanceActivityListener(
311 mMaintenanceActivityListener);
312 }
313 } catch (RemoteException e) {
314 Log.e(TAG, "Fail to unregister listener.", e);
315 }
316 }
317
318 private final class MaintenanceActivityListener extends IMaintenanceActivityListener.Stub {
319 @Override
320 public void onMaintenanceActivityChanged(final boolean active) {
321 DefaultDeviceIdleController.this.setMaintenanceActivity(active);
322 }
323 }
324 }
Yao Chen3a7976d2016-01-20 17:27:08 -0800325}