Home | History | Annotate | Download | only in car
      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
    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 
    114     public GarageModeService(Context context, CarPowerManagementService powerManagementService) {
    115         this(context, powerManagementService, null);
    116     }
    117 
    118     @VisibleForTesting
    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
    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
    155     public void release() {
    156         logd("release GarageModeService");
    157         mDeviceIdleController.stopTracking();
    158         mContentObserver.unregister();
    159     }
    160 
    161     @Override
    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
    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
    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
    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
    212     public void onSleepExit() {
    213         // ignored
    214     }
    215 
    216     @Override
    217     public void onSleepEntry() {
    218         synchronized (this) {
    219             mInGarageMode = false;
    220         }
    221     }
    222 
    223     @Override
    224     public void onShutdown() {
    225         synchronized (this) {
    226             mHandler.sendMessage(
    227                     mHandler.obtainMessage(MSG_WRITE_TO_PREF, mGarageModeIndex, 0));
    228         }
    229     }
    230 
    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 
    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
    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 {
    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          */
    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
    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
    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
    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
    348             public void onMaintenanceActivityChanged(final boolean active) {
    349                 DefaultDeviceIdleController.this.setMaintenanceActivity(active);
    350             }
    351         }
    352     }
    353 
    354     private void logd(String msg) {
    355         if (DBG) {
    356             Log.d(TAG, msg);
    357         }
    358     }
    359 
    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 
    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