blob: f9d562d3d57f7c4ee737f504e1536c17079a6790 [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;
29
30import java.io.PrintWriter;
31import java.util.Calendar;
32
33/**
34 * Controls car garage mode.
35 *
36 * Car garage mode is a time window for the car to do maintenance work when the car is not in use.
37 * The {@link com.android.car.GarageModeService} interacts with {@link com.android.car.CarPowerManagementService}
38 * to start and end garage mode. A {@link com.android.car.GarageModeService.GarageModePolicy} defines
39 * when the garage mode should start and how long it should last.
40 */
41public class GarageModeService implements CarServiceBase,
42 CarPowerManagementService.PowerEventProcessingHandler,
43 CarPowerManagementService.PowerServiceEventListener{
44 private static String TAG = "GarageModeService";
45 private static String GARAGE_MODE_PREFERENCE_FILE = "com.android.car.PREFERENCE_FILE_KEY";
46 private static String GARAGE_MODE_INDEX = "garage_mode_index";
47
48 private static final int MSG_EXIT_GARAGE_MODE_EARLY = 0;
49 private static final int MSG_WRITE_TO_PREF = 1;
50
51 // TODO: move this to garage mode policy too.
52 private static final int MAINTENANCE_WINDOW = 5 * 60 * 1000; // 5 minutes
53 // wait for 10 seconds to allow maintenance activities to start (e.g., connecting to wifi).
54 private static final int MAINTENANCE_ACTIVITY_START_GRACE_PERIOUD = 10 * 1000;
55
56 private final CarPowerManagementService mPowerManagementService;
57 protected final Context mContext;
58
59 @GuardedBy("this")
60 private boolean mInGarageMode;
61 @GuardedBy("this")
62 private boolean mMaintenanceActive;
63 @GuardedBy("this")
64 private int mGarageModeIndex;
65
66 private final Object mPolicyLock = new Object();
67 @GuardedBy("mPolicyLock")
68 private GarageModePolicy mPolicy;
69
70 private SharedPreferences mSharedPreferences;
71
72 private IDeviceIdleController mDeviceIdleController;
73 private GarageModeHandler mHandler = new GarageModeHandler();
74
75 private MaintenanceActivityListener mMaintenanceActivityListener
76 = new MaintenanceActivityListener();
77
78 private class GarageModeHandler extends Handler {
79 @Override
80 public void handleMessage(Message msg) {
81 switch (msg.what) {
82 case MSG_EXIT_GARAGE_MODE_EARLY:
83 mPowerManagementService.notifyPowerEventProcessingCompletion(
84 GarageModeService.this);
85 break;
86 case MSG_WRITE_TO_PREF:
87 writeToPref(msg.arg1);
88 break;
89 }
90 }
91 }
92
93 public GarageModeService(Context context, CarPowerManagementService powerManagementService) {
94 mContext = context;
95 mPowerManagementService = powerManagementService;
96 init();
97 }
98
99 @Override
100 public void init() {
101 Log.d(TAG, "init GarageMode");
102 mSharedPreferences = mContext.getSharedPreferences(
103 GARAGE_MODE_PREFERENCE_FILE, Context.MODE_PRIVATE);
104 synchronized (mPolicyLock) {
105 readPolicyLocked();
106 }
107
108 mDeviceIdleController = IDeviceIdleController.Stub.asInterface(
109 ServiceManager.getService(Context.DEVICE_IDLE_CONTROLLER));
110 final int index = mSharedPreferences.getInt(GARAGE_MODE_INDEX, 0);
111
112 synchronized (this) {
113 try {
114 mGarageModeIndex = index;
115 mMaintenanceActive = mDeviceIdleController.registerMaintenanceActivityListener(
116 mMaintenanceActivityListener);
117 } catch (RemoteException e) {
118 Log.e(TAG, "Unable to register listener with DeviceIdleController", e);
119 }
120 }
121 mPowerManagementService.registerPowerEventProcessingHandler(this);
122 }
123
124 @Override
125 public void release() {
126 Log.d(TAG, "release GarageModeService");
127 try {
128 mDeviceIdleController.unregisterMaintenanceActivityListener(mMaintenanceActivityListener);
129 } catch (RemoteException e) {
130 Log.w(TAG, "Fail to unregister listener with DeviceIdleController", e);
131 }
132 }
133
134 @Override
135 public void dump(PrintWriter writer) {
136 writer.println("mGarageModeIndex: " + mGarageModeIndex);
137 writer.println("inGarageMode? " + mInGarageMode);
138 }
139
140 @Override
141 public long onPrepareShutdown(boolean shuttingDown) {
142 // this is the beginning of each garage mode.
143 synchronized (this) {
144 Log.d(TAG, "onPrePowerEvent " + shuttingDown);
145 mInGarageMode = true;
146 mGarageModeIndex++;
147 mHandler.removeMessages(MSG_EXIT_GARAGE_MODE_EARLY);
148 if (!mMaintenanceActive) {
149 mHandler.sendMessageDelayed(
150 mHandler.obtainMessage(MSG_EXIT_GARAGE_MODE_EARLY),
151 MAINTENANCE_ACTIVITY_START_GRACE_PERIOUD);
152 }
153 // We always reserve the maintenance window first. If later, we found no
154 // maintenance work active, we will exit garage mode early after
155 // MAINTENANCE_ACTIVITY_START_GRACE_PERIOUD
156 return MAINTENANCE_WINDOW;
157 }
158 }
159
160 @Override
161 public void onPowerOn(boolean displayOn) {
162 synchronized (this) {
163 Log.d(TAG, "onPowerOn: " + displayOn);
164 if (displayOn) {
165 // the car is use now. reset the garage mode counter.
166 mGarageModeIndex = 0;
167 }
168 }
169 }
170
171 @Override
172 public int getWakeupTime() {
173 final int index;
174 synchronized (this) {
175 index = mGarageModeIndex;
176 }
177 synchronized (mPolicyLock) {
178 return mPolicy.getNextWakeUpTime(index);
179 }
180 }
181
182 @Override
183 public void onSleepExit() {
184 // ignored
185 }
186
187 @Override
188 public void onSleepEntry() {
189 synchronized (this) {
190 mInGarageMode = false;
191 }
192 }
193
194 @Override
195 public void onShutdown() {
196 synchronized (this) {
197 mHandler.sendMessage(
198 mHandler.obtainMessage(MSG_WRITE_TO_PREF, mGarageModeIndex, 0));
199 }
200 }
201
202 private void readPolicyLocked() {
203 Log.d(TAG, "readPolicyLocked");
204 // TODO: define a xml schema for garage mode policy and read it from system dir.
205 mPolicy = new DefaultGarageModePolicy();
206 }
207
208 private void writeToPref(int index) {
209 SharedPreferences.Editor editor = mSharedPreferences.edit();
210 editor.putInt(GARAGE_MODE_INDEX, index);
211 editor.commit();
212 }
213
214 private void onMaintenanceActivityChanged(boolean active) {
215 boolean shouldReportCompletion = false;
216 synchronized (this) {
217 Log.d(TAG, "onMaintenanceActivityChanged: " + active);
218 if (!mInGarageMode || mMaintenanceActive == active) {
219 return;
220 }
221 mMaintenanceActive = active;
222
223 if (!active) {
224 shouldReportCompletion = true;
225 mInGarageMode = false;
226 } else {
227 // we are in garage mode, and maintenance work has just begun.
228 mHandler.removeMessages(MSG_EXIT_GARAGE_MODE_EARLY);
229 }
230 }
231 if (shouldReportCompletion) {
232 // we are in garage mode, and maintenance work has finished.
233 mPowerManagementService.notifyPowerEventProcessingCompletion(this);
234 }
235 }
236
237 private final class MaintenanceActivityListener extends IMaintenanceActivityListener.Stub {
238 @Override
239 public void onMaintenanceActivityChanged(final boolean active) {
240 GarageModeService.this.onMaintenanceActivityChanged(active);
241 }
242 }
243
244 public abstract static class GarageModePolicy {
245 abstract public int getNextWakeUpTime(int index);
246 /**
247 * Returns number of seconds between now to 1am {@param numDays} days later.
248 */
249 public static int nextWakeUpSeconds(int numDays) {
250 // TODO: Should select a random time within a window to avoid all cars update at the
251 // same time.
252 Calendar next = Calendar.getInstance();
253 next.add(Calendar.DATE, numDays);
254 next.set(Calendar.HOUR_OF_DAY, 1);
255 next.set(Calendar.MINUTE, 0);
256 next.set(Calendar.SECOND, 0);
257
258 Calendar now = Calendar.getInstance();
259 return (next.get(Calendar.MILLISECOND) - now.get(Calendar.MILLISECOND)) / 1000;
260 }
261 }
262
263 /**
264 * Default garage mode policy.
265 *
266 * The first wake up time is set to be 1am the next day. And it keeps waking up every day for a
267 * week. After that, wake up every 7 days for a month, and wake up every 30 days thereafter.
268 */
269 private static class DefaultGarageModePolicy extends GarageModePolicy {
270 private static final int COL_INDEX = 0;
271 private static final int COL_WAKEUP_TIME = 1;
272
273 private static final int[][] WAKE_UP_TIME = new int[][] {
274 {7 /*index <= 7*/, 1 /* wake up the next day */},
275 {11 /* 7 < index <= 11 */, 7 /* wake up the next week */},
276 {Integer.MAX_VALUE /* index > 11 */, 30 /* wake up the next month */}
277 };
278
279 @Override
280 public int getNextWakeUpTime(int index) {
281 for (int i = 0; i < WAKE_UP_TIME.length; i++) {
282 if (index <= WAKE_UP_TIME[i][COL_INDEX]) {
283 return nextWakeUpSeconds(WAKE_UP_TIME[i][COL_WAKEUP_TIME]);
284 }
285 }
286
287 Log.w(TAG, "Integer.MAX number of wake ups... How long have we been sleeping? ");
288 return 0;
289 }
290 }
291}