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; |
| 29 | |
| 30 | import java.io.PrintWriter; |
| 31 | import 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 | */ |
| 41 | public 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 | } |