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.car.CarApiUtil; 19 import android.car.settings.CarSettings; 20 import android.car.settings.GarageModeSettingsObserver; 21 import android.content.Context; 22 import android.content.SharedPreferences; 23 import android.net.Uri; 24 import android.os.Handler; 25 import android.os.IDeviceIdleController; 26 import android.os.IMaintenanceActivityListener; 27 import android.os.Message; 28 import android.os.RemoteException; 29 import android.os.ServiceManager; 30 import android.preference.PreferenceManager; 31 import android.provider.Settings; 32 import android.util.Log; 33 34 import com.android.internal.annotations.GuardedBy; 35 import com.android.internal.annotations.VisibleForTesting; 36 37 import java.io.PrintWriter; 38 import java.util.Calendar; 39 40 import static android.car.settings.GarageModeSettingsObserver.GARAGE_MODE_ENABLED_URI; 41 import static android.car.settings.GarageModeSettingsObserver.GARAGE_MODE_MAINTENANCE_WINDOW_URI; 42 import static android.car.settings.GarageModeSettingsObserver.GARAGE_MODE_WAKE_UP_TIME_URI; 43 44 /** 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 */ 52 public class GarageModeService implements CarServiceBase, 53 CarPowerManagementService.PowerEventProcessingHandler, 54 CarPowerManagementService.PowerServiceEventListener, 55 DeviceIdleControllerWrapper.DeviceMaintenanceActivityListener { 56 private static String TAG = "GarageModeService"; 57 private static final boolean DBG = false; 58 59 private static final int MSG_EXIT_GARAGE_MODE_EARLY = 0; 60 private static final int MSG_WRITE_TO_PREF = 1; 61 62 private static final String KEY_GARAGE_MODE_INDEX = "garage_mode_index"; 63 64 // wait for 10 seconds to allow maintenance activities to start (e.g., connecting to wifi). 65 protected static final int MAINTENANCE_ACTIVITY_START_GRACE_PERIOUD = 10 * 1000; 66 67 private final CarPowerManagementService mPowerManagementService; 68 protected final Context mContext; 69 70 @VisibleForTesting 71 @GuardedBy("this") 72 protected boolean mInGarageMode; 73 @VisibleForTesting 74 @GuardedBy("this") 75 protected boolean mMaintenanceActive; 76 @VisibleForTesting 77 @GuardedBy("this") 78 protected int mGarageModeIndex; 79 80 @GuardedBy("this") 81 @VisibleForTesting 82 protected int mMaintenanceWindow; 83 @GuardedBy("this") 84 private GarageModePolicy mPolicy; 85 @GuardedBy("this") 86 private int mWakeUpHour = 0; 87 @GuardedBy("this") 88 private int mWakeUpMin = 0; 89 @GuardedBy("this") 90 private boolean mGarageModeEnabled; 91 92 93 private SharedPreferences mSharedPreferences; 94 private final GarageModeSettingsObserver mContentObserver; 95 96 private DeviceIdleControllerWrapper mDeviceIdleController; 97 private final GarageModeHandler mHandler = new GarageModeHandler(); 98 99 private class GarageModeHandler extends Handler { 100 @Override handleMessage(Message msg)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 GarageModeService(Context context, CarPowerManagementService powerManagementService)114 public GarageModeService(Context context, CarPowerManagementService powerManagementService) { 115 this(context, powerManagementService, null); 116 } 117 118 @VisibleForTesting GarageModeService(Context context, CarPowerManagementService powerManagementService, DeviceIdleControllerWrapper deviceIdleController)119 protected GarageModeService(Context context, CarPowerManagementService powerManagementService, 120 DeviceIdleControllerWrapper deviceIdleController) { 121 mContext = context; 122 mPowerManagementService = powerManagementService; 123 if (deviceIdleController == null) { 124 mDeviceIdleController = new DefaultDeviceIdleController(); 125 } else { 126 mDeviceIdleController = deviceIdleController; 127 } 128 mContentObserver = new GarageModeSettingsObserver(mContext, mHandler) { 129 @Override 130 public void onChange(boolean selfChange, Uri uri) { 131 onSettingsChangedInternal(uri); 132 } 133 }; 134 } 135 136 @Override init()137 public void init() { 138 logd("init GarageMode"); 139 mSharedPreferences = PreferenceManager.getDefaultSharedPreferences( 140 mContext.createDeviceProtectedStorageContext()); 141 final int index = mSharedPreferences.getInt(KEY_GARAGE_MODE_INDEX, 0); 142 synchronized (this) { 143 mMaintenanceActive = mDeviceIdleController.startTracking(this); 144 mGarageModeIndex = index; 145 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); 149 } 150 mContentObserver.register(); 151 mPowerManagementService.registerPowerEventProcessingHandler(this); 152 } 153 154 @Override release()155 public void release() { 156 logd("release GarageModeService"); 157 mDeviceIdleController.stopTracking(); 158 mContentObserver.unregister(); 159 } 160 161 @Override dump(PrintWriter writer)162 public void dump(PrintWriter writer) { 163 writer.println("mGarageModeIndex: " + mGarageModeIndex); 164 writer.println("inGarageMode? " + mInGarageMode); 165 writer.println("GarageModeTime: " + mWakeUpHour + ":" + mWakeUpMin); 166 writer.println("GarageModeEnabled " + mGarageModeEnabled); 167 writer.println("GarageModeTimeWindow " + mMaintenanceWindow + " ms"); 168 } 169 170 @Override onPrepareShutdown(boolean shuttingDown)171 public long onPrepareShutdown(boolean shuttingDown) { 172 // this is the beginning of each garage mode. 173 synchronized (this) { 174 logd("onPrePowerEvent " + shuttingDown); 175 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 186 return mMaintenanceWindow; 187 } 188 } 189 190 @Override onPowerOn(boolean displayOn)191 public void onPowerOn(boolean displayOn) { 192 synchronized (this) { 193 logd("onPowerOn: " + displayOn); 194 if (displayOn) { 195 // the car is use now. reset the garage mode counter. 196 mGarageModeIndex = 0; 197 } 198 } 199 } 200 201 @Override getWakeupTime()202 public int getWakeupTime() { 203 synchronized (this) { 204 if (!mGarageModeEnabled) { 205 return 0; 206 } 207 return mPolicy.getNextWakeUpTime(mGarageModeIndex, mWakeUpHour, mWakeUpMin); 208 } 209 } 210 211 @Override onSleepExit()212 public void onSleepExit() { 213 // ignored 214 } 215 216 @Override onSleepEntry()217 public void onSleepEntry() { 218 synchronized (this) { 219 mInGarageMode = false; 220 } 221 } 222 223 @Override onShutdown()224 public void onShutdown() { 225 synchronized (this) { 226 mHandler.sendMessage( 227 mHandler.obtainMessage(MSG_WRITE_TO_PREF, mGarageModeIndex, 0)); 228 } 229 } 230 readPolicyLocked()231 private void readPolicyLocked() { 232 logd("readPolicy"); 233 // TODO: define a xml schema for policy and read it from system dir. bug: 32096969 234 mPolicy = new DefaultGarageModePolicy(); 235 } 236 writeToPref(int index)237 private void writeToPref(int index) { 238 SharedPreferences.Editor editor = mSharedPreferences.edit(); 239 editor.putInt(KEY_GARAGE_MODE_INDEX, index); 240 editor.commit(); 241 } 242 243 @Override onMaintenanceActivityChanged(boolean active)244 public void onMaintenanceActivityChanged(boolean active) { 245 boolean shouldReportCompletion = false; 246 synchronized (this) { 247 logd("onMaintenanceActivityChanged: " + active); 248 mMaintenanceActive = active; 249 if (!mInGarageMode) { 250 return; 251 } 252 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 267 public abstract static class GarageModePolicy { getNextWakeUpTime(int index, int hour, int min)268 abstract public int getNextWakeUpTime(int index, int hour, int min); 269 /** 270 * Returns number of seconds between now to 1am {@param numDays} days later. 271 */ nextWakeUpSeconds(int numDays, int hour, int min)272 public static int nextWakeUpSeconds(int numDays, int hour, int min) { 273 // TODO: Should select a random time within a window. bug: 32096386 274 // This is to avoid all cars update at the same time. 275 Calendar next = Calendar.getInstance(); 276 next.add(Calendar.DATE, numDays); 277 next.set(Calendar.HOUR_OF_DAY, hour); 278 next.set(Calendar.MINUTE, min); 279 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[][] { 297 {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 */} 300 }; 301 302 @Override getNextWakeUpTime(int index, int hour, int min)303 public int getNextWakeUpTime(int index, int hour, int min) { 304 for (int i = 0; i < WAKE_UP_TIME.length; i++) { 305 if (index <= WAKE_UP_TIME[i][COL_INDEX]) { 306 return nextWakeUpSeconds(WAKE_UP_TIME[i][COL_WAKEUP_TIME], hour, min); 307 } 308 } 309 310 Log.w(TAG, "Integer.MAX number of wake ups... How long have we been sleeping? "); 311 return 0; 312 } 313 } 314 315 private static class DefaultDeviceIdleController extends DeviceIdleControllerWrapper { 316 private IDeviceIdleController mDeviceIdleController; 317 private MaintenanceActivityListener mMaintenanceActivityListener 318 = new MaintenanceActivityListener(); 319 320 @Override startLocked()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 stopTracking()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 onMaintenanceActivityChanged(final boolean active)348 public void onMaintenanceActivityChanged(final boolean active) { 349 DefaultDeviceIdleController.this.setMaintenanceActivity(active); 350 } 351 } 352 } 353 logd(String msg)354 private void logd(String msg) { 355 if (DBG) { 356 Log.d(TAG, msg); 357 } 358 } 359 readFromSettingsLocked(String... keys)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 onSettingsChangedInternal(Uri uri)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 } 400 } 401