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
17package com.android.car;
18
19import static android.os.SystemClock.elapsedRealtime;
20
21import android.annotation.Nullable;
22import android.app.Service;
23import android.content.Intent;
24import android.content.pm.PackageManager;
25import android.hardware.automotive.vehicle.V2_0.IVehicle;
26import android.os.Binder;
27import android.os.Build;
28import android.os.IBinder;
29import android.os.IHwBinder.DeathRecipient;
30import android.os.RemoteException;
31import android.os.ServiceManager;
32import android.os.SystemClock;
33import android.os.SystemProperties;
34import android.util.Log;
35
36import com.android.car.systeminterface.SystemInterface;
37
38import com.android.internal.annotations.VisibleForTesting;
39import com.android.internal.util.RingBufferIndices;
40
41import java.io.FileDescriptor;
42import java.io.PrintWriter;
43import java.util.NoSuchElementException;
44
45public class CarService extends Service {
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
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        SystemProperties.set("boot.car_service_created", "1");
99
100        linkToDeath(mVehicle, mVehicleDeathRecipient);
101
102        ServiceManager.addService("car_service", mICarImpl);
103        super.onCreate();
104    }
105
106    // onDestroy is best-effort and might not get called on shutdown/reboot. As such it is not
107    // suitable for permanently saving state or other need-to-happen operation. If you have a
108    // cleanup task that you want to make sure happens on shutdown/reboot, see OnShutdownReboot.
109    @Override
110    public void onDestroy() {
111        Log.i(CarLog.TAG_SERVICE, "Service onDestroy");
112        mICarImpl.release();
113        mCanBusErrorNotifier.removeFailureReport(this);
114
115        if (mVehicle != null) {
116            try {
117                mVehicle.unlinkToDeath(mVehicleDeathRecipient);
118                mVehicle = null;
119            } catch (RemoteException e) {
120                // Ignore errors on shutdown path.
121            }
122        }
123
124        super.onDestroy();
125    }
126
127    @Override
128    public int onStartCommand(Intent intent, int flags, int startId) {
129        // keep it alive.
130        return START_STICKY;
131    }
132
133    @Override
134    public IBinder onBind(Intent intent) {
135        return mICarImpl;
136    }
137
138    @Override
139    protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
140        // historically, the way to get a dumpsys from CarService has been to use
141        // "dumpsys activity service com.android.car/.CarService" - leaving this
142        // as a forward to car_service makes the previously well-known command still work
143        mICarImpl.dump(fd, writer, args);
144    }
145
146    @Nullable
147    private IVehicle getVehicleWithTimeout(long waitMilliseconds) {
148        IVehicle vehicle = getVehicle();
149        long start = elapsedRealtime();
150        while (vehicle == null && (start + waitMilliseconds) > elapsedRealtime()) {
151            try {
152                Thread.sleep(100);
153            } catch (InterruptedException e) {
154                throw new RuntimeException("Sleep was interrupted", e);
155            }
156
157            vehicle = getVehicle();
158        }
159
160        if (vehicle != null) {
161            mCanBusErrorNotifier.removeFailureReport(this);
162        }
163
164        return vehicle;
165    }
166
167    @Nullable
168    private static IVehicle getVehicle() {
169        try {
170            return android.hardware.automotive.vehicle.V2_0.IVehicle.getService();
171        } catch (RemoteException e) {
172            Log.e(CarLog.TAG_SERVICE, "Failed to get IVehicle service", e);
173        } catch (NoSuchElementException e) {
174            Log.e(CarLog.TAG_SERVICE, "IVehicle service not registered yet");
175        }
176        return null;
177    }
178
179    private class VehicleDeathRecipient implements DeathRecipient {
180        private int deathCount = 0;
181
182        @Override
183        public void serviceDied(long cookie) {
184            Log.w(CarLog.TAG_SERVICE, "Vehicle HAL died.");
185
186            try {
187                mVehicle.unlinkToDeath(this);
188            } catch (RemoteException e) {
189                Log.e(CarLog.TAG_SERVICE, "Failed to unlinkToDeath", e);  // Log and continue.
190            }
191            mVehicle = null;
192
193            mVhalCrashTracker.crashDetected();
194
195            Log.i(CarLog.TAG_SERVICE, "Trying to reconnect to Vehicle HAL: " +
196                    mVehicleInterfaceName);
197            mVehicle = getVehicleWithTimeout(WAIT_FOR_VEHICLE_HAL_TIMEOUT_MS);
198            if (mVehicle == null) {
199                throw new IllegalStateException("Failed to reconnect to Vehicle HAL");
200            }
201
202            linkToDeath(mVehicle, this);
203
204            Log.i(CarLog.TAG_SERVICE, "Notifying car service Vehicle HAL reconnected...");
205            mICarImpl.vehicleHalReconnected(mVehicle);
206        }
207    }
208
209    private static void linkToDeath(IVehicle vehicle, DeathRecipient recipient) {
210        try {
211            vehicle.linkToDeath(recipient, 0);
212        } catch (RemoteException e) {
213            throw new IllegalStateException("Failed to linkToDeath Vehicle HAL");
214        }
215    }
216
217    @VisibleForTesting
218    static class CrashTracker {
219        private final int mMaxCrashCountLimit;
220        private final int mSlidingWindowMillis;
221
222        private final long[] mCrashTimestamps;
223        private final RingBufferIndices mCrashTimestampsIndices;
224        private final Runnable mCallback;
225
226        /**
227         * If maxCrashCountLimit number of crashes occurred within slidingWindowMillis time
228         * frame then call provided callback function.
229         */
230        CrashTracker(int maxCrashCountLimit, int slidingWindowMillis, Runnable callback) {
231            mMaxCrashCountLimit = maxCrashCountLimit;
232            mSlidingWindowMillis = slidingWindowMillis;
233            mCallback = callback;
234
235            mCrashTimestamps = new long[maxCrashCountLimit];
236            mCrashTimestampsIndices = new RingBufferIndices(mMaxCrashCountLimit);
237        }
238
239        void crashDetected() {
240            long lastCrash = SystemClock.elapsedRealtime();
241            mCrashTimestamps[mCrashTimestampsIndices.add()] = lastCrash;
242
243            if (mCrashTimestampsIndices.size() == mMaxCrashCountLimit) {
244                long firstCrash = mCrashTimestamps[mCrashTimestampsIndices.indexOf(0)];
245
246                if (lastCrash - firstCrash < mSlidingWindowMillis) {
247                    mCallback.run();
248                }
249            }
250        }
251    }
252}
253