1 /*
2  * Copyright (C) 2016 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 
17 package com.android.car.systeminterface;
18 
19 import android.content.BroadcastReceiver;
20 import android.content.Context;
21 import android.content.Intent;
22 import android.content.IntentFilter;
23 import android.os.Looper;
24 import android.os.PowerManager;
25 import android.util.Log;
26 import android.util.Pair;
27 
28 import com.android.car.procfsinspector.ProcessInfo;
29 import com.android.car.procfsinspector.ProcfsInspector;
30 import com.android.internal.annotations.VisibleForTesting;
31 import com.android.internal.car.ICarServiceHelper;
32 
33 import java.time.Duration;
34 import java.util.ArrayList;
35 import java.util.List;
36 import java.util.concurrent.CountDownLatch;
37 import java.util.concurrent.Executors;
38 import java.util.concurrent.ScheduledExecutorService;
39 import java.util.concurrent.TimeUnit;
40 
41 /**
42  * Interface that abstracts system status (booted, sleeping, ...) operations
43  */
44 public interface SystemStateInterface {
45     static final String TAG = SystemStateInterface.class.getSimpleName();
shutdown()46     void shutdown();
47     /**
48      * Put the device into Suspend to RAM mode
49      * @return boolean true if suspend succeeded
50      */
enterDeepSleep()51     boolean enterDeepSleep();
scheduleActionForBootCompleted(Runnable action, Duration delay)52     void scheduleActionForBootCompleted(Runnable action, Duration delay);
53 
isWakeupCausedByTimer()54     default boolean isWakeupCausedByTimer() {
55         //TODO bug: 32061842, check wake up reason and do necessary operation information should
56         // come from kernel. it can be either power on or wake up for maintenance
57         // power on will involve GPIO trigger from power controller
58         // its own wakeup will involve timer expiration.
59         return false;
60     }
61 
isSystemSupportingDeepSleep()62     default boolean isSystemSupportingDeepSleep() {
63         //TODO should return by checking some kernel suspend control sysfs, bug: 32061842
64         return true;
65     }
66 
getRunningProcesses()67     default List<ProcessInfo> getRunningProcesses() {
68         return ProcfsInspector.readProcessTable();
69     }
70 
setCarServiceHelper(ICarServiceHelper helper)71     default void setCarServiceHelper(ICarServiceHelper helper) {
72         // Do nothing
73     }
74 
75     /**
76      * Default implementation that is used internally.
77      */
78     @VisibleForTesting
79     class DefaultImpl implements SystemStateInterface {
80         private static final int MAX_WAIT_FOR_HELPER_SEC = 10;
81         private static final Duration MIN_BOOT_COMPLETE_ACTION_DELAY = Duration.ofSeconds(10);
82         private static final int SUSPEND_TRY_TIMEOUT_MS = 1_000;
83 
84         private ICarServiceHelper mICarServiceHelper; // mHelperLatch becomes 0 when this is set
85         private final CountDownLatch mHelperLatch = new CountDownLatch(1);
86         private final Context mContext;
87         private final PowerManager mPowerManager;
88         private List<Pair<Runnable, Duration>> mActionsList = new ArrayList<>();
89         private ScheduledExecutorService mExecutorService;
90         private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
91             @Override
92             public void onReceive(Context context, Intent intent) {
93                 if (Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())) {
94                     for (Pair<Runnable, Duration> action : mActionsList) {
95                         mExecutorService.schedule(action.first,
96                             action.second.toMillis(), TimeUnit.MILLISECONDS);
97                     }
98                 }
99             }
100         };
101 
102         @VisibleForTesting
DefaultImpl(Context context)103         public DefaultImpl(Context context) {
104             mContext = context;
105             mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
106         }
107 
108         @Override
shutdown()109         public void shutdown() {
110             mPowerManager.shutdown(false /* no confirm*/, null, true /* true */);
111         }
112 
113         @Override
enterDeepSleep()114         public boolean enterDeepSleep() {
115             // TODO(b/32061842) Set wake up time via VHAL
116             if (!canInvokeHelper()) {
117                 return false;
118             }
119 
120             boolean deviceEnteredSleep = false;
121             try {
122                 int retVal = mICarServiceHelper.forceSuspend(SUSPEND_TRY_TIMEOUT_MS);
123                 deviceEnteredSleep = retVal == 0;
124             } catch (Exception e) {
125                 Log.e(TAG, "Unable to enter deep sleep", e);
126             }
127             return deviceEnteredSleep;
128         }
129 
130         // Checks if mICarServiceHelper is available. (It might be unavailable if
131         // we are asked to shut down before we're completely up and running.)
132         // If the helper is null, wait for it to be set.
133         // Returns true if the helper is available.
canInvokeHelper()134         private boolean canInvokeHelper() {
135             if (Looper.myLooper() == Looper.getMainLooper()) {
136                 // We should not be called from the main thread!
137                 throw new IllegalStateException("SystemStateInterface.enterDeepSleep() "
138                         + "was called from the main thread");
139             }
140             if (mICarServiceHelper != null) {
141                 return true;
142             }
143             // We have no helper. If we wait, maybe we will get a helper.
144             try {
145                 mHelperLatch.await(MAX_WAIT_FOR_HELPER_SEC, TimeUnit.SECONDS);
146             } catch (InterruptedException ie) {
147                 Thread.currentThread().interrupt(); // Restore interrupted status
148             }
149             if (mICarServiceHelper != null) {
150                 return true;
151             }
152             Log.e(TAG, "Unable to enter deep sleep: ICarServiceHelper is still null "
153                     + "after waiting " + MAX_WAIT_FOR_HELPER_SEC + " seconds");
154             return false;
155         }
156 
157         @Override
scheduleActionForBootCompleted(Runnable action, Duration delay)158         public void scheduleActionForBootCompleted(Runnable action, Duration delay) {
159             if (MIN_BOOT_COMPLETE_ACTION_DELAY.compareTo(delay) < 0) {
160                 // TODO: consider adding some degree of randomness here
161                 delay = MIN_BOOT_COMPLETE_ACTION_DELAY;
162             }
163             if (mActionsList.isEmpty()) {
164                 final int corePoolSize = 1;
165                 mExecutorService = Executors.newScheduledThreadPool(corePoolSize);
166                 IntentFilter intentFilter = new IntentFilter(Intent.ACTION_BOOT_COMPLETED);
167                 mContext.registerReceiver(mBroadcastReceiver, intentFilter);
168             }
169             mActionsList.add(Pair.create(action, delay));
170         }
171 
172         @Override
setCarServiceHelper(ICarServiceHelper helper)173         public void setCarServiceHelper(ICarServiceHelper helper) {
174             mICarServiceHelper = helper;
175             mHelperLatch.countDown();
176         }
177     }
178 }
179