Yao Chen | 3a7976d | 2016-01-20 17:27:08 -0800 | [diff] [blame] | 1 | /* |
| 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 | */ |
| 16 | package com.android.car; |
| 17 | |
| 18 | import android.content.Context; |
| 19 | import android.content.SharedPreferences; |
| 20 | import android.os.Handler; |
| 21 | import android.os.IDeviceIdleController; |
| 22 | import android.os.IMaintenanceActivityListener; |
| 23 | import android.os.Message; |
| 24 | import android.os.RemoteException; |
| 25 | import android.os.ServiceManager; |
| 26 | import android.util.Log; |
| 27 | |
| 28 | import com.android.internal.annotations.GuardedBy; |
Yao Chen | dacd724 | 2016-01-26 14:42:42 -0800 | [diff] [blame] | 29 | import com.android.internal.annotations.VisibleForTesting; |
Yao Chen | 3a7976d | 2016-01-20 17:27:08 -0800 | [diff] [blame] | 30 | |
| 31 | import java.io.PrintWriter; |
| 32 | import 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 | */ |
| 42 | public class GarageModeService implements CarServiceBase, |
| 43 | CarPowerManagementService.PowerEventProcessingHandler, |
Yao Chen | dacd724 | 2016-01-26 14:42:42 -0800 | [diff] [blame] | 44 | CarPowerManagementService.PowerServiceEventListener, |
| 45 | DeviceIdleControllerWrapper.DeviceMaintenanceActivityListener { |
Yao Chen | 3a7976d | 2016-01-20 17:27:08 -0800 | [diff] [blame] | 46 | 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 Chen | dacd724 | 2016-01-26 14:42:42 -0800 | [diff] [blame] | 54 | @VisibleForTesting |
| 55 | protected static final int MAINTENANCE_WINDOW = 5 * 60 * 1000; // 5 minutes |
Yao Chen | 3a7976d | 2016-01-20 17:27:08 -0800 | [diff] [blame] | 56 | // wait for 10 seconds to allow maintenance activities to start (e.g., connecting to wifi). |
Yao Chen | dacd724 | 2016-01-26 14:42:42 -0800 | [diff] [blame] | 57 | protected static final int MAINTENANCE_ACTIVITY_START_GRACE_PERIOUD = 10 * 1000; |
Yao Chen | 3a7976d | 2016-01-20 17:27:08 -0800 | [diff] [blame] | 58 | |
| 59 | private final CarPowerManagementService mPowerManagementService; |
| 60 | protected final Context mContext; |
| 61 | |
Yao Chen | dacd724 | 2016-01-26 14:42:42 -0800 | [diff] [blame] | 62 | @VisibleForTesting |
Yao Chen | 3a7976d | 2016-01-20 17:27:08 -0800 | [diff] [blame] | 63 | @GuardedBy("this") |
Yao Chen | dacd724 | 2016-01-26 14:42:42 -0800 | [diff] [blame] | 64 | protected boolean mInGarageMode; |
| 65 | @VisibleForTesting |
Yao Chen | 3a7976d | 2016-01-20 17:27:08 -0800 | [diff] [blame] | 66 | @GuardedBy("this") |
Yao Chen | dacd724 | 2016-01-26 14:42:42 -0800 | [diff] [blame] | 67 | protected boolean mMaintenanceActive; |
| 68 | @VisibleForTesting |
Yao Chen | 3a7976d | 2016-01-20 17:27:08 -0800 | [diff] [blame] | 69 | @GuardedBy("this") |
Yao Chen | dacd724 | 2016-01-26 14:42:42 -0800 | [diff] [blame] | 70 | protected int mGarageModeIndex; |
Yao Chen | 3a7976d | 2016-01-20 17:27:08 -0800 | [diff] [blame] | 71 | |
| 72 | private final Object mPolicyLock = new Object(); |
| 73 | @GuardedBy("mPolicyLock") |
| 74 | private GarageModePolicy mPolicy; |
| 75 | |
| 76 | private SharedPreferences mSharedPreferences; |
| 77 | |
Yao Chen | dacd724 | 2016-01-26 14:42:42 -0800 | [diff] [blame] | 78 | private DeviceIdleControllerWrapper mDeviceIdleController; |
Yao Chen | 3a7976d | 2016-01-20 17:27:08 -0800 | [diff] [blame] | 79 | private GarageModeHandler mHandler = new GarageModeHandler(); |
| 80 | |
Yao Chen | 3a7976d | 2016-01-20 17:27:08 -0800 | [diff] [blame] | 81 | 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 Chen | dacd724 | 2016-01-26 14:42:42 -0800 | [diff] [blame] | 97 | this(context, powerManagementService, null); |
| 98 | } |
| 99 | |
| 100 | @VisibleForTesting |
| 101 | protected GarageModeService(Context context, CarPowerManagementService powerManagementService, |
| 102 | DeviceIdleControllerWrapper deviceIdleController) { |
Yao Chen | 3a7976d | 2016-01-20 17:27:08 -0800 | [diff] [blame] | 103 | mContext = context; |
| 104 | mPowerManagementService = powerManagementService; |
Yao Chen | dacd724 | 2016-01-26 14:42:42 -0800 | [diff] [blame] | 105 | if (deviceIdleController == null) { |
| 106 | mDeviceIdleController = new DefaultDeviceIdleController(); |
| 107 | } else { |
| 108 | mDeviceIdleController = deviceIdleController; |
| 109 | } |
Yao Chen | 3a7976d | 2016-01-20 17:27:08 -0800 | [diff] [blame] | 110 | } |
| 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 Chen | 3a7976d | 2016-01-20 17:27:08 -0800 | [diff] [blame] | 121 | final int index = mSharedPreferences.getInt(GARAGE_MODE_INDEX, 0); |
Yao Chen | 3a7976d | 2016-01-20 17:27:08 -0800 | [diff] [blame] | 122 | synchronized (this) { |
Yao Chen | dacd724 | 2016-01-26 14:42:42 -0800 | [diff] [blame] | 123 | mMaintenanceActive = mDeviceIdleController.startTracking(this); |
| 124 | mGarageModeIndex = index; |
Yao Chen | 3a7976d | 2016-01-20 17:27:08 -0800 | [diff] [blame] | 125 | } |
| 126 | mPowerManagementService.registerPowerEventProcessingHandler(this); |
| 127 | } |
| 128 | |
| 129 | @Override |
| 130 | public void release() { |
| 131 | Log.d(TAG, "release GarageModeService"); |
Yao Chen | dacd724 | 2016-01-26 14:42:42 -0800 | [diff] [blame] | 132 | mDeviceIdleController.stopTracking(); |
Yao Chen | 3a7976d | 2016-01-20 17:27:08 -0800 | [diff] [blame] | 133 | } |
| 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 Chen | dacd724 | 2016-01-26 14:42:42 -0800 | [diff] [blame] | 215 | @Override |
| 216 | public void onMaintenanceActivityChanged(boolean active) { |
Yao Chen | 3a7976d | 2016-01-20 17:27:08 -0800 | [diff] [blame] | 217 | boolean shouldReportCompletion = false; |
| 218 | synchronized (this) { |
| 219 | Log.d(TAG, "onMaintenanceActivityChanged: " + active); |
Yao Chen | dacd724 | 2016-01-26 14:42:42 -0800 | [diff] [blame] | 220 | mMaintenanceActive = active; |
| 221 | if (!mInGarageMode) { |
Yao Chen | 3a7976d | 2016-01-20 17:27:08 -0800 | [diff] [blame] | 222 | return; |
| 223 | } |
Yao Chen | 3a7976d | 2016-01-20 17:27:08 -0800 | [diff] [blame] | 224 | |
| 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 Chen | 3a7976d | 2016-01-20 17:27:08 -0800 | [diff] [blame] | 239 | 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 Chen | dacd724 | 2016-01-26 14:42:42 -0800 | [diff] [blame] | 286 | |
| 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 Chen | 3a7976d | 2016-01-20 17:27:08 -0800 | [diff] [blame] | 325 | } |