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 
17 package com.android.car;
18 
19 import static android.os.SystemClock.elapsedRealtime;
20 
21 import android.annotation.Nullable;
22 import android.app.Service;
23 import android.content.Intent;
24 import android.hardware.automotive.vehicle.V2_0.IVehicle;
25 import android.os.Build;
26 import android.os.IBinder;
27 import android.os.IHwBinder.DeathRecipient;
28 import android.os.Process;
29 import android.os.RemoteException;
30 import android.os.ServiceManager;
31 import android.os.SystemClock;
32 import android.os.SystemProperties;
33 import android.util.Log;
34 
35 import com.android.car.systeminterface.SystemInterface;
36 import com.android.internal.annotations.VisibleForTesting;
37 import com.android.internal.util.RingBufferIndices;
38 
39 import java.io.FileDescriptor;
40 import java.io.PrintWriter;
41 import java.util.NoSuchElementException;
42 
43 public class CarService extends Service {
44 
45     private static final boolean RESTART_CAR_SERVICE_WHEN_VHAL_CRASH = true;
46 
47     private static final long WAIT_FOR_VEHICLE_HAL_TIMEOUT_MS = 10_000;
48 
49     private static final boolean IS_USER_BUILD = "user".equals(Build.TYPE);
50 
51     private CanBusErrorNotifier mCanBusErrorNotifier;
52     private ICarImpl mICarImpl;
53     private IVehicle mVehicle;
54 
55     private String mVehicleInterfaceName;
56 
57     // If 10 crashes of Vehicle HAL occurred within 10 minutes then thrown an exception in
58     // Car Service.
59     private final CrashTracker mVhalCrashTracker = new CrashTracker(
60             10,  // Max crash count.
61             10 * 60 * 1000,  // 10 minutes - sliding time window.
62             () -> {
63                 if (IS_USER_BUILD) {
64                     Log.e(CarLog.TAG_SERVICE, "Vehicle HAL keeps crashing, notifying user...");
65                     mCanBusErrorNotifier.reportFailure(CarService.this);
66                 } else {
67                     throw new RuntimeException(
68                             "Vehicle HAL crashed too many times in a given time frame");
69                 }
70             }
71     );
72 
73     private final VehicleDeathRecipient mVehicleDeathRecipient = new VehicleDeathRecipient();
74 
75     @Override
onCreate()76     public void onCreate() {
77         Log.i(CarLog.TAG_SERVICE, "Service onCreate");
78         mCanBusErrorNotifier = new CanBusErrorNotifier(this /* context */);
79         mVehicle = getVehicle();
80 
81         if (mVehicle == null) {
82             throw new IllegalStateException("Vehicle HAL service is not available.");
83         }
84         try {
85             mVehicleInterfaceName = mVehicle.interfaceDescriptor();
86         } catch (RemoteException e) {
87             throw new IllegalStateException("Unable to get Vehicle HAL interface descriptor", e);
88         }
89 
90         Log.i(CarLog.TAG_SERVICE, "Connected to " + mVehicleInterfaceName);
91 
92         mICarImpl = new ICarImpl(this,
93                 mVehicle,
94                 SystemInterface.Builder.defaultSystemInterface(this).build(),
95                 mCanBusErrorNotifier,
96                 mVehicleInterfaceName);
97         mICarImpl.init();
98 
99         linkToDeath(mVehicle, mVehicleDeathRecipient);
100 
101         ServiceManager.addService("car_service", mICarImpl);
102         ServiceManager.addService("car_stats", mICarImpl.getStatsService());
103         SystemProperties.set("boot.car_service_created", "1");
104         super.onCreate();
105     }
106 
107     // onDestroy is best-effort and might not get called on shutdown/reboot. As such it is not
108     // suitable for permanently saving state or other need-to-happen operation. If you have a
109     // cleanup task that you want to make sure happens on shutdown/reboot, see OnShutdownReboot.
110     @Override
onDestroy()111     public void onDestroy() {
112         Log.i(CarLog.TAG_SERVICE, "Service onDestroy");
113         mICarImpl.release();
114         mCanBusErrorNotifier.removeFailureReport(this);
115 
116         if (mVehicle != null) {
117             try {
118                 mVehicle.unlinkToDeath(mVehicleDeathRecipient);
119                 mVehicle = null;
120             } catch (RemoteException e) {
121                 // Ignore errors on shutdown path.
122             }
123         }
124 
125         super.onDestroy();
126     }
127 
128     @Override
onStartCommand(Intent intent, int flags, int startId)129     public int onStartCommand(Intent intent, int flags, int startId) {
130         // keep it alive.
131         return START_STICKY;
132     }
133 
134     @Override
onBind(Intent intent)135     public IBinder onBind(Intent intent) {
136         return mICarImpl;
137     }
138 
139     @Override
dump(FileDescriptor fd, PrintWriter writer, String[] args)140     protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
141         // historically, the way to get a dumpsys from CarService has been to use
142         // "dumpsys activity service com.android.car/.CarService" - leaving this
143         // as a forward to car_service makes the previously well-known command still work
144         mICarImpl.dump(fd, writer, args);
145     }
146 
147     @Nullable
getVehicleWithTimeout(long waitMilliseconds)148     private IVehicle getVehicleWithTimeout(long waitMilliseconds) {
149         IVehicle vehicle = getVehicle();
150         long start = elapsedRealtime();
151         while (vehicle == null && (start + waitMilliseconds) > elapsedRealtime()) {
152             try {
153                 Thread.sleep(100);
154             } catch (InterruptedException e) {
155                 throw new RuntimeException("Sleep was interrupted", e);
156             }
157 
158             vehicle = getVehicle();
159         }
160 
161         if (vehicle != null) {
162             mCanBusErrorNotifier.removeFailureReport(this);
163         }
164 
165         return vehicle;
166     }
167 
168     @Nullable
getVehicle()169     private static IVehicle getVehicle() {
170         try {
171             return android.hardware.automotive.vehicle.V2_0.IVehicle.getService();
172         } catch (RemoteException e) {
173             Log.e(CarLog.TAG_SERVICE, "Failed to get IVehicle service", e);
174         } catch (NoSuchElementException e) {
175             Log.e(CarLog.TAG_SERVICE, "IVehicle service not registered yet");
176         }
177         return null;
178     }
179 
180     private class VehicleDeathRecipient implements DeathRecipient {
181         private int deathCount = 0;
182 
183         @Override
serviceDied(long cookie)184         public void serviceDied(long cookie) {
185             if (RESTART_CAR_SERVICE_WHEN_VHAL_CRASH) {
186                 Log.wtf(CarLog.TAG_SERVICE, "***Vehicle HAL died. Car service will restart***");
187                 Process.killProcess(Process.myPid());
188                 return;
189             }
190 
191             Log.wtf(CarLog.TAG_SERVICE, "***Vehicle HAL died.***");
192 
193             try {
194                 mVehicle.unlinkToDeath(this);
195             } catch (RemoteException e) {
196                 Log.e(CarLog.TAG_SERVICE, "Failed to unlinkToDeath", e);  // Log and continue.
197             }
198             mVehicle = null;
199 
200             mVhalCrashTracker.crashDetected();
201 
202             Log.i(CarLog.TAG_SERVICE, "Trying to reconnect to Vehicle HAL: " +
203                     mVehicleInterfaceName);
204             mVehicle = getVehicleWithTimeout(WAIT_FOR_VEHICLE_HAL_TIMEOUT_MS);
205             if (mVehicle == null) {
206                 throw new IllegalStateException("Failed to reconnect to Vehicle HAL");
207             }
208 
209             linkToDeath(mVehicle, this);
210 
211             Log.i(CarLog.TAG_SERVICE, "Notifying car service Vehicle HAL reconnected...");
212             mICarImpl.vehicleHalReconnected(mVehicle);
213         }
214     }
215 
linkToDeath(IVehicle vehicle, DeathRecipient recipient)216     private static void linkToDeath(IVehicle vehicle, DeathRecipient recipient) {
217         try {
218             vehicle.linkToDeath(recipient, 0);
219         } catch (RemoteException e) {
220             throw new IllegalStateException("Failed to linkToDeath Vehicle HAL");
221         }
222     }
223 
224     @VisibleForTesting
225     static class CrashTracker {
226         private final int mMaxCrashCountLimit;
227         private final int mSlidingWindowMillis;
228 
229         private final long[] mCrashTimestamps;
230         private final RingBufferIndices mCrashTimestampsIndices;
231         private final Runnable mCallback;
232 
233         /**
234          * If maxCrashCountLimit number of crashes occurred within slidingWindowMillis time
235          * frame then call provided callback function.
236          */
CrashTracker(int maxCrashCountLimit, int slidingWindowMillis, Runnable callback)237         CrashTracker(int maxCrashCountLimit, int slidingWindowMillis, Runnable callback) {
238             mMaxCrashCountLimit = maxCrashCountLimit;
239             mSlidingWindowMillis = slidingWindowMillis;
240             mCallback = callback;
241 
242             mCrashTimestamps = new long[maxCrashCountLimit];
243             mCrashTimestampsIndices = new RingBufferIndices(mMaxCrashCountLimit);
244         }
245 
crashDetected()246         void crashDetected() {
247             long lastCrash = SystemClock.elapsedRealtime();
248             mCrashTimestamps[mCrashTimestampsIndices.add()] = lastCrash;
249 
250             if (mCrashTimestampsIndices.size() == mMaxCrashCountLimit) {
251                 long firstCrash = mCrashTimestamps[mCrashTimestampsIndices.indexOf(0)];
252 
253                 if (lastCrash - firstCrash < mSlidingWindowMillis) {
254                     mCallback.run();
255                 }
256             }
257         }
258     }
259 }
260