| /* |
| * Copyright (C) 2019 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.experimentalcar; |
| |
| import android.annotation.FloatRange; |
| import android.annotation.NonNull; |
| import android.annotation.Nullable; |
| import android.car.Car; |
| import android.car.VehiclePropertyIds; |
| import android.car.experimental.DriverAwarenessEvent; |
| import android.car.experimental.DriverAwarenessSupplierConfig; |
| import android.car.experimental.DriverAwarenessSupplierService; |
| import android.car.experimental.DriverDistractionChangeEvent; |
| import android.car.experimental.ExperimentalCar; |
| import android.car.experimental.IDriverAwarenessSupplier; |
| import android.car.experimental.IDriverAwarenessSupplierCallback; |
| import android.car.experimental.IDriverDistractionChangeListener; |
| import android.car.experimental.IDriverDistractionManager; |
| import android.car.hardware.CarPropertyValue; |
| import android.car.hardware.property.CarPropertyEvent; |
| import android.car.hardware.property.CarPropertyManager; |
| import android.content.ComponentName; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.ServiceConnection; |
| import android.os.Handler; |
| import android.os.HandlerThread; |
| import android.os.IBinder; |
| import android.os.Looper; |
| import android.os.RemoteCallbackList; |
| import android.os.RemoteException; |
| import android.os.UserHandle; |
| import android.util.Log; |
| import android.util.Pair; |
| |
| import com.android.car.CarServiceBase; |
| import com.android.car.Utils; |
| import com.android.internal.annotations.GuardedBy; |
| import com.android.internal.annotations.VisibleForTesting; |
| |
| import java.io.PrintWriter; |
| import java.util.ArrayDeque; |
| import java.util.ArrayList; |
| import java.util.Comparator; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.TimerTask; |
| |
| /** |
| * Driver Distraction Service for using the driver's awareness, the required awareness of the |
| * driving environment to expose APIs for the driver's current distraction level. |
| * |
| * <p>Allows the registration of multiple {@link IDriverAwarenessSupplier} so that higher accuracy |
| * signals can be used when possible, with a fallback to less accurate signals. The {@link |
| * TouchDriverAwarenessSupplier} is always set to the fallback implementation - it is configured |
| * to send change-events, so its data will not become stale. |
| */ |
| public final class DriverDistractionExperimentalFeatureService extends |
| IDriverDistractionManager.Stub implements CarServiceBase { |
| |
| private static final String TAG = "CAR.DriverDistractionService"; |
| |
| private static final float DEFAULT_AWARENESS_VALUE_FOR_LOG = 1.0f; |
| private static final float MOVING_REQUIRED_AWARENESS = 1.0f; |
| private static final float STATIONARY_REQUIRED_AWARENESS = 0.0f; |
| private static final int MAX_EVENT_LOG_COUNT = 50; |
| private static final int PROPERTY_UPDATE_RATE_HZ = 5; |
| @VisibleForTesting |
| static final float DEFAULT_AWARENESS_PERCENTAGE = 1.0f; |
| |
| private final HandlerThread mClientDispatchHandlerThread; |
| private final Handler mClientDispatchHandler; |
| |
| private final Object mLock = new Object(); |
| |
| @GuardedBy("mLock") |
| private final ArrayDeque<Utils.TransitionLog> mTransitionLogs = new ArrayDeque<>(); |
| |
| /** |
| * All the active service connections. |
| */ |
| @GuardedBy("mLock") |
| private final List<ServiceConnection> mServiceConnections = new ArrayList<>(); |
| |
| /** |
| * The binder for each supplier. |
| */ |
| @GuardedBy("mLock") |
| private final Map<ComponentName, IDriverAwarenessSupplier> mSupplierBinders = new HashMap<>(); |
| |
| /** |
| * The configuration for each supplier. |
| */ |
| @GuardedBy("mLock") |
| private final Map<IDriverAwarenessSupplier, DriverAwarenessSupplierConfig> mSupplierConfigs = |
| new HashMap<>(); |
| |
| /** |
| * List of driver awareness suppliers that can be used to understand the current driver |
| * awareness level. Ordered from highest to lowest priority. |
| */ |
| @GuardedBy("mLock") |
| private final List<IDriverAwarenessSupplier> mPrioritizedDriverAwarenessSuppliers = |
| new ArrayList<>(); |
| |
| /** |
| * Helper map for looking up the priority rank of a supplier by name. A higher integer value |
| * represents a higher priority. |
| */ |
| @GuardedBy("mLock") |
| private final Map<IDriverAwarenessSupplier, Integer> mDriverAwarenessSupplierPriorities = |
| new HashMap<>(); |
| |
| /** |
| * List of clients listening to UX restriction events. |
| */ |
| private final RemoteCallbackList<IDriverDistractionChangeListener> mDistractionClients = |
| new RemoteCallbackList<>(); |
| |
| /** |
| * Comparator used to sort {@link #mDriverAwarenessSupplierPriorities}. |
| */ |
| private final Comparator<IDriverAwarenessSupplier> mPrioritizedSuppliersComparator = |
| (left, right) -> { |
| int leftPri = mDriverAwarenessSupplierPriorities.get(left); |
| int rightPri = mDriverAwarenessSupplierPriorities.get(right); |
| // sort descending |
| return rightPri - leftPri; |
| }; |
| |
| /** |
| * Keep track of the most recent awareness event for each supplier for use when the data from |
| * higher priority suppliers becomes stale. This is necessary in order to seamlessly handle |
| * fallback scenarios when data from preferred providers becomes stale. |
| */ |
| @GuardedBy("mLock") |
| private final Map<IDriverAwarenessSupplier, DriverAwarenessEventWrapper> |
| mCurrentAwarenessEventsMap = |
| new HashMap<>(); |
| |
| /** |
| * The awareness event that is currently being used to determine the driver awareness level. |
| * |
| * <p>This is null until it is set by the first awareness supplier to send an event |
| */ |
| @GuardedBy("mLock") |
| @Nullable |
| private DriverAwarenessEventWrapper mCurrentDriverAwareness; |
| |
| /** |
| * Timer to alert when the current driver awareness event has become stale. |
| */ |
| @GuardedBy("mLock") |
| private ITimer mExpiredDriverAwarenessTimer; |
| |
| /** |
| * The current, non-stale, driver distraction event. Defaults to 100% awareness. |
| */ |
| @GuardedBy("mLock") |
| private DriverDistractionChangeEvent mCurrentDistractionEvent; |
| |
| /** |
| * The required driver awareness based on the current driving environment, where 1.0 means that |
| * full awareness is required and 0.0 means than no awareness is required. |
| */ |
| @FloatRange(from = 0.0f, to = 1.0f) |
| @GuardedBy("mLock") |
| private float mRequiredAwareness = STATIONARY_REQUIRED_AWARENESS; |
| |
| @GuardedBy("mLock") |
| private Car mCar; |
| |
| @GuardedBy("mLock") |
| private CarPropertyManager mPropertyManager; |
| |
| private final Context mContext; |
| private final ITimeSource mTimeSource; |
| private final Looper mLooper; |
| |
| /** |
| * Create an instance of {@link DriverDistractionExperimentalFeatureService}. |
| * |
| * @param context the context |
| * @param timeSource the source that provides the current time |
| * @param timer the timer used for scheduling |
| */ |
| DriverDistractionExperimentalFeatureService( |
| Context context, |
| ITimeSource timeSource, |
| ITimer timer) { |
| this(context, timeSource, timer, Looper.myLooper()); |
| } |
| |
| @VisibleForTesting |
| DriverDistractionExperimentalFeatureService( |
| Context context, |
| ITimeSource timeSource, |
| ITimer timer, |
| Looper looper) { |
| mContext = context; |
| mTimeSource = timeSource; |
| mExpiredDriverAwarenessTimer = timer; |
| mCurrentDistractionEvent = new DriverDistractionChangeEvent.Builder() |
| .setElapsedRealtimeTimestamp(mTimeSource.elapsedRealtime()) |
| .setAwarenessPercentage(DEFAULT_AWARENESS_PERCENTAGE) |
| .build(); |
| mClientDispatchHandlerThread = new HandlerThread(TAG); |
| mClientDispatchHandlerThread.start(); |
| mClientDispatchHandler = new Handler(mClientDispatchHandlerThread.getLooper()); |
| mLooper = looper; |
| } |
| |
| @Override |
| public void init() { |
| // The touch supplier is an internal implementation, so it can be started initiated by its |
| // constructor, unlike other suppliers |
| ComponentName touchComponent = new ComponentName(mContext, |
| TouchDriverAwarenessSupplier.class); |
| TouchDriverAwarenessSupplier touchSupplier = new TouchDriverAwarenessSupplier(mContext, |
| new DriverAwarenessSupplierCallback(touchComponent), mLooper); |
| addDriverAwarenessSupplier(touchComponent, touchSupplier, /* priority= */ 0); |
| touchSupplier.onReady(); |
| |
| String[] preferredDriverAwarenessSuppliers = mContext.getResources().getStringArray( |
| R.array.preferredDriverAwarenessSuppliers); |
| for (int i = 0; i < preferredDriverAwarenessSuppliers.length; i++) { |
| String supplierStringName = preferredDriverAwarenessSuppliers[i]; |
| ComponentName externalComponent = ComponentName.unflattenFromString(supplierStringName); |
| // the touch supplier has priority 0 and preferred suppliers are higher based on order |
| int priority = i + 1; |
| bindDriverAwarenessSupplierService(externalComponent, priority); |
| } |
| |
| synchronized (mLock) { |
| mCar = Car.createCar(mContext); |
| if (mCar != null) { |
| mPropertyManager = (CarPropertyManager) mCar.getCarManager(Car.PROPERTY_SERVICE); |
| } else { |
| Log.e(TAG, "Unable to connect to car in init"); |
| } |
| } |
| |
| if (mPropertyManager != null) { |
| mPropertyManager.registerCallback(mSpeedPropertyEventCallback, |
| VehiclePropertyIds.PERF_VEHICLE_SPEED, |
| PROPERTY_UPDATE_RATE_HZ); |
| } else { |
| Log.e(TAG, "Unable to get car property service."); |
| } |
| } |
| |
| @Override |
| public void release() { |
| logd("release"); |
| mDistractionClients.kill(); |
| synchronized (mLock) { |
| for (ServiceConnection serviceConnection : mServiceConnections) { |
| mContext.unbindService(serviceConnection); |
| } |
| if (mPropertyManager != null) { |
| mPropertyManager.unregisterCallback(mSpeedPropertyEventCallback); |
| } |
| if (mCar != null) { |
| mCar.disconnect(); |
| } |
| } |
| } |
| |
| @Override |
| public void dump(PrintWriter writer) { |
| writer.println("*DriverDistractionExperimentalFeatureService*"); |
| mDistractionClients.dump(writer, "Distraction Clients "); |
| writer.println("Prioritized Driver Awareness Suppliers (highest to lowest priority):"); |
| synchronized (mLock) { |
| for (int i = 0; i < mPrioritizedDriverAwarenessSuppliers.size(); i++) { |
| writer.println( |
| String.format(" %d: %s", i, mPrioritizedDriverAwarenessSuppliers.get( |
| i).getClass().getName())); |
| } |
| writer.println("Current Driver Awareness:"); |
| writer.println(" Value: " |
| + (mCurrentDriverAwareness == null ? "unknown" |
| : mCurrentDriverAwareness.mAwarenessEvent.getAwarenessValue())); |
| writer.println(" Supplier: " + (mCurrentDriverAwareness == null ? "unknown" |
| : mCurrentDriverAwareness.mSupplier.getClass().getSimpleName())); |
| writer.println(" Timestamp (ms since boot): " |
| + (mCurrentDriverAwareness == null ? "unknown" |
| : mCurrentDriverAwareness.mAwarenessEvent.getTimeStamp())); |
| writer.println("Current Required Awareness: " + mRequiredAwareness); |
| writer.println("Last Distraction Event:"); |
| writer.println(" Value: " |
| + (mCurrentDistractionEvent == null ? "unknown" |
| : mCurrentDistractionEvent.getAwarenessPercentage())); |
| writer.println(" Timestamp (ms since boot): " |
| + (mCurrentDistractionEvent == null ? "unknown" |
| : mCurrentDistractionEvent.getElapsedRealtimeTimestamp())); |
| writer.println("Change log:"); |
| for (Utils.TransitionLog log : mTransitionLogs) { |
| writer.println(log); |
| } |
| } |
| } |
| |
| /** |
| * Bind to a {@link DriverAwarenessSupplierService} by its component name. |
| * |
| * @param componentName the name of the {@link DriverAwarenessSupplierService} to bind to. |
| * @param priority the priority rank of this supplier |
| */ |
| private void bindDriverAwarenessSupplierService(ComponentName componentName, int priority) { |
| Intent intent = new Intent(); |
| intent.setComponent(componentName); |
| ServiceConnection connection = new DriverAwarenessServiceConnection(priority); |
| synchronized (mLock) { |
| mServiceConnections.add(connection); |
| } |
| if (!mContext.bindServiceAsUser(intent, connection, |
| Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT, UserHandle.SYSTEM)) { |
| Log.e(TAG, "Unable to bind with intent: " + intent); |
| // TODO(b/146471650) attempt to rebind |
| } |
| } |
| |
| @VisibleForTesting |
| void handleDriverAwarenessEvent(DriverAwarenessEventWrapper awarenessEventWrapper) { |
| synchronized (mLock) { |
| handleDriverAwarenessEventLocked(awarenessEventWrapper); |
| } |
| } |
| |
| /** |
| * Handle the driver awareness event by: |
| * <ul> |
| * <li>Cache the driver awareness event for its supplier</li> |
| * <li>Update the current awareness value</li> |
| * <li>Register to refresh the awareness value again when the new current expires</li> |
| * </ul> |
| * |
| * @param awarenessEventWrapper the driver awareness event that has occurred |
| */ |
| @GuardedBy("mLock") |
| private void handleDriverAwarenessEventLocked( |
| DriverAwarenessEventWrapper awarenessEventWrapper) { |
| // update the current awareness event for the supplier, checking that it is the newest event |
| IDriverAwarenessSupplier supplier = awarenessEventWrapper.mSupplier; |
| long timestamp = awarenessEventWrapper.mAwarenessEvent.getTimeStamp(); |
| if (!mCurrentAwarenessEventsMap.containsKey(supplier) |
| || mCurrentAwarenessEventsMap.get(supplier).mAwarenessEvent.getTimeStamp() |
| < timestamp) { |
| mCurrentAwarenessEventsMap.put(awarenessEventWrapper.mSupplier, awarenessEventWrapper); |
| } |
| |
| int oldSupplierPriority = mDriverAwarenessSupplierPriorities.get(supplier); |
| float oldAwarenessValue = DEFAULT_AWARENESS_VALUE_FOR_LOG; |
| if (mCurrentDriverAwareness != null) { |
| oldAwarenessValue = mCurrentDriverAwareness.mAwarenessEvent.getAwarenessValue(); |
| } |
| |
| updateCurrentAwarenessValueLocked(); |
| |
| int newSupplierPriority = mDriverAwarenessSupplierPriorities.get( |
| mCurrentDriverAwareness.mSupplier); |
| if (mSupplierConfigs.get(mCurrentDriverAwareness.mSupplier).getMaxStalenessMillis() |
| != DriverAwarenessSupplierService.NO_STALENESS |
| && newSupplierPriority >= oldSupplierPriority) { |
| // only reschedule an expiration if this is for a supplier that is the same or higher |
| // priority than the old value. If there is a higher priority supplier with non-stale |
| // data, then mCurrentDriverAwareness won't change even though we received a new event. |
| scheduleExpirationTimerLocked(); |
| } |
| |
| if (oldAwarenessValue != mCurrentDriverAwareness.mAwarenessEvent.getAwarenessValue()) { |
| logd("Driver awareness updated: " |
| + mCurrentDriverAwareness.mAwarenessEvent.getAwarenessValue()); |
| addTransitionLogLocked(oldAwarenessValue, |
| awarenessEventWrapper.mAwarenessEvent.getAwarenessValue(), |
| "Driver awareness updated by " |
| + awarenessEventWrapper.mSupplier.getClass().getSimpleName()); |
| } |
| |
| updateCurrentDistractionEventLocked(); |
| } |
| |
| /** |
| * Get the current awareness value. |
| */ |
| @VisibleForTesting |
| DriverAwarenessEventWrapper getCurrentDriverAwareness() { |
| return mCurrentDriverAwareness; |
| } |
| |
| /** |
| * Set the drier awareness suppliers. Allows circumventing the {@link #init()} logic. |
| */ |
| @VisibleForTesting |
| void setDriverAwarenessSuppliers( |
| List<Pair<IDriverAwarenessSupplier, DriverAwarenessSupplierConfig>> suppliers) { |
| mPrioritizedDriverAwarenessSuppliers.clear(); |
| mDriverAwarenessSupplierPriorities.clear(); |
| for (int i = 0; i < suppliers.size(); i++) { |
| Pair<IDriverAwarenessSupplier, DriverAwarenessSupplierConfig> pair = suppliers.get(i); |
| mSupplierConfigs.put(pair.first, pair.second); |
| mDriverAwarenessSupplierPriorities.put(pair.first, i); |
| mPrioritizedDriverAwarenessSuppliers.add(pair.first); |
| } |
| mPrioritizedDriverAwarenessSuppliers.sort(mPrioritizedSuppliersComparator); |
| } |
| |
| /** |
| * {@link CarPropertyEvent} listener registered with the {@link CarPropertyManager} for getting |
| * speed change notifications. |
| */ |
| private final CarPropertyManager.CarPropertyEventCallback mSpeedPropertyEventCallback = |
| new CarPropertyManager.CarPropertyEventCallback() { |
| @Override |
| public void onChangeEvent(CarPropertyValue value) { |
| synchronized (mLock) { |
| handleSpeedEventLocked(value); |
| } |
| } |
| |
| @Override |
| public void onErrorEvent(int propId, int zone) { |
| Log.e(TAG, "Error in callback for vehicle speed"); |
| } |
| }; |
| |
| |
| @VisibleForTesting |
| @GuardedBy("mLock") |
| void handleSpeedEventLocked(@NonNull CarPropertyValue value) { |
| if (value.getPropertyId() != VehiclePropertyIds.PERF_VEHICLE_SPEED) { |
| Log.e(TAG, "Unexpected property id: " + value.getPropertyId()); |
| return; |
| } |
| |
| float oldValue = mRequiredAwareness; |
| if ((Float) value.getValue() > 0) { |
| mRequiredAwareness = MOVING_REQUIRED_AWARENESS; |
| } else { |
| mRequiredAwareness = STATIONARY_REQUIRED_AWARENESS; |
| } |
| |
| if (Float.compare(oldValue, mRequiredAwareness) != 0) { |
| logd("Required awareness updated: " + mRequiredAwareness); |
| addTransitionLogLocked(oldValue, mRequiredAwareness, "Required awareness"); |
| updateCurrentDistractionEventLocked(); |
| } |
| } |
| |
| @GuardedBy("mLock") |
| private void updateCurrentDistractionEventLocked() { |
| if (mCurrentDriverAwareness == null) { |
| logd("Driver awareness level is not yet known"); |
| return; |
| } |
| float awarenessPercentage; |
| if (mRequiredAwareness == 0) { |
| // avoid divide by 0 error - awareness percentage should be 100% when required |
| // awareness is 0 |
| awarenessPercentage = 1.0f; |
| } else { |
| // Cap awareness percentage at 100% |
| awarenessPercentage = Math.min( |
| mCurrentDriverAwareness.mAwarenessEvent.getAwarenessValue() |
| / mRequiredAwareness, 1.0f); |
| } |
| if (Float.compare(mCurrentDistractionEvent.getAwarenessPercentage(), awarenessPercentage) |
| == 0) { |
| // no need to dispatch unless there's a change |
| return; |
| } |
| |
| addTransitionLogLocked(mCurrentDistractionEvent.getAwarenessPercentage(), |
| awarenessPercentage, "Awareness percentage"); |
| |
| mCurrentDistractionEvent = new DriverDistractionChangeEvent.Builder() |
| .setElapsedRealtimeTimestamp(mTimeSource.elapsedRealtime()) |
| .setAwarenessPercentage(awarenessPercentage) |
| .build(); |
| |
| // TODO(b/148231321) throttle to emit events at most once every 50ms |
| DriverDistractionChangeEvent changeEvent = mCurrentDistractionEvent; |
| mClientDispatchHandler.post( |
| () -> dispatchCurrentDistractionEventToClientsLocked(changeEvent)); |
| } |
| |
| @GuardedBy("mLock") |
| private void dispatchCurrentDistractionEventToClientsLocked( |
| DriverDistractionChangeEvent changeEvent) { |
| logd("Dispatching event to clients: " + changeEvent); |
| int numClients = mDistractionClients.beginBroadcast(); |
| for (int i = 0; i < numClients; i++) { |
| IDriverDistractionChangeListener callback = mDistractionClients.getBroadcastItem(i); |
| try { |
| callback.onDriverDistractionChange(changeEvent); |
| } catch (RemoteException ignores) { |
| // ignore |
| } |
| } |
| mDistractionClients.finishBroadcast(); |
| } |
| |
| /** |
| * Internally register the supplier with the specified priority. |
| */ |
| private void addDriverAwarenessSupplier( |
| ComponentName componentName, |
| IDriverAwarenessSupplier awarenessSupplier, |
| int priority) { |
| synchronized (mLock) { |
| mSupplierBinders.put(componentName, awarenessSupplier); |
| mDriverAwarenessSupplierPriorities.put(awarenessSupplier, priority); |
| mPrioritizedDriverAwarenessSuppliers.add(awarenessSupplier); |
| mPrioritizedDriverAwarenessSuppliers.sort(mPrioritizedSuppliersComparator); |
| } |
| } |
| |
| /** |
| * Remove references to a supplier. |
| */ |
| private void removeDriverAwarenessSupplier(ComponentName componentName) { |
| synchronized (mLock) { |
| IDriverAwarenessSupplier supplier = mSupplierBinders.get(componentName); |
| mSupplierBinders.remove(componentName); |
| mDriverAwarenessSupplierPriorities.remove(supplier); |
| mPrioritizedDriverAwarenessSuppliers.remove(supplier); |
| } |
| } |
| |
| /** |
| * Update {@link #mCurrentDriverAwareness} based on the current driver awareness events for each |
| * supplier. |
| */ |
| @GuardedBy("mLock") |
| private void updateCurrentAwarenessValueLocked() { |
| for (IDriverAwarenessSupplier supplier : mPrioritizedDriverAwarenessSuppliers) { |
| long supplierMaxStaleness = mSupplierConfigs.get(supplier).getMaxStalenessMillis(); |
| DriverAwarenessEventWrapper eventForSupplier = mCurrentAwarenessEventsMap.get(supplier); |
| if (eventForSupplier == null) { |
| continue; |
| } |
| if (supplierMaxStaleness == DriverAwarenessSupplierService.NO_STALENESS) { |
| // this supplier can't be stale, so use its information |
| mCurrentDriverAwareness = eventForSupplier; |
| return; |
| } |
| |
| long oldestFreshTimestamp = mTimeSource.elapsedRealtime() - supplierMaxStaleness; |
| if (eventForSupplier.mAwarenessEvent.getTimeStamp() > oldestFreshTimestamp) { |
| // value is still fresh, so use it |
| mCurrentDriverAwareness = eventForSupplier; |
| return; |
| } |
| } |
| |
| if (mCurrentDriverAwareness == null) { |
| // There must always at least be a fallback supplier with NO_STALENESS configuration. |
| // Since we control this configuration, getting this exception represents a developer |
| // error in initialization. |
| throw new IllegalStateException( |
| "Unable to determine the current driver awareness value"); |
| } |
| } |
| |
| /** |
| * Sets a timer to update the refresh the awareness value once the current value has become |
| * stale. |
| */ |
| @GuardedBy("mLock") |
| private void scheduleExpirationTimerLocked() { |
| // reschedule the current awareness expiration task |
| mExpiredDriverAwarenessTimer.reset(); |
| long delay = mCurrentDriverAwareness.mAwarenessEvent.getTimeStamp() |
| - mTimeSource.elapsedRealtime() |
| + mCurrentDriverAwareness.mMaxStaleness; |
| if (delay < 0) { |
| // somehow the event is already stale |
| synchronized (mLock) { |
| updateCurrentAwarenessValueLocked(); |
| } |
| return; |
| } |
| mExpiredDriverAwarenessTimer.schedule(new TimerTask() { |
| @Override |
| public void run() { |
| logd("Driver awareness has become stale. Selecting new awareness level."); |
| synchronized (mLock) { |
| updateCurrentAwarenessValueLocked(); |
| updateCurrentDistractionEventLocked(); |
| } |
| } |
| }, delay); |
| |
| logd(String.format( |
| "Current awareness value is stale after %sms and is scheduled to expire in %sms", |
| mCurrentDriverAwareness.mMaxStaleness, delay)); |
| } |
| |
| /** |
| * Add the state change to the transition log. |
| * |
| * @param oldValue the old value |
| * @param newValue the new value |
| * @param extra name of the value being changed |
| */ |
| @GuardedBy("mLock") |
| private void addTransitionLogLocked(float oldValue, float newValue, String extra) { |
| if (mTransitionLogs.size() >= MAX_EVENT_LOG_COUNT) { |
| mTransitionLogs.remove(); |
| } |
| |
| Utils.TransitionLog tLog = new Utils.TransitionLog(TAG, oldValue, newValue, |
| System.currentTimeMillis(), extra); |
| mTransitionLogs.add(tLog); |
| } |
| |
| private static void logd(String message) { |
| if (Log.isLoggable(TAG, Log.DEBUG)) { |
| Log.d(TAG, message); |
| } |
| } |
| |
| @Override |
| public DriverDistractionChangeEvent getLastDistractionEvent() throws RemoteException { |
| IExperimentalCarImpl.assertPermission(mContext, |
| ExperimentalCar.PERMISSION_READ_CAR_DRIVER_DISTRACTION); |
| synchronized (mLock) { |
| return mCurrentDistractionEvent; |
| } |
| } |
| |
| @Override |
| public void addDriverDistractionChangeListener(IDriverDistractionChangeListener listener) |
| throws RemoteException { |
| IExperimentalCarImpl.assertPermission(mContext, |
| ExperimentalCar.PERMISSION_READ_CAR_DRIVER_DISTRACTION); |
| if (listener == null) { |
| throw new IllegalArgumentException("IDriverDistractionChangeListener is null"); |
| } |
| mDistractionClients.register(listener); |
| |
| DriverDistractionChangeEvent changeEvent = mCurrentDistractionEvent; |
| mClientDispatchHandler.post(() -> { |
| try { |
| listener.onDriverDistractionChange(changeEvent); |
| } catch (RemoteException ignores) { |
| // ignore |
| } |
| }); |
| } |
| |
| |
| @Override |
| public void removeDriverDistractionChangeListener(IDriverDistractionChangeListener listener) |
| throws RemoteException { |
| IExperimentalCarImpl.assertPermission(mContext, |
| ExperimentalCar.PERMISSION_READ_CAR_DRIVER_DISTRACTION); |
| if (listener == null) { |
| Log.e(TAG, "unregisterUxRestrictionsChangeListener(): listener null"); |
| throw new IllegalArgumentException("Listener is null"); |
| } |
| mDistractionClients.unregister(listener); |
| } |
| |
| /** |
| * The service connection between this distraction service and a {@link |
| * DriverAwarenessSupplierService}, communicated through {@link IDriverAwarenessSupplier}. |
| */ |
| private class DriverAwarenessServiceConnection implements ServiceConnection { |
| |
| final int mPriority; |
| |
| /** |
| * Create an instance of {@link DriverAwarenessServiceConnection}. |
| * |
| * @param priority the priority of the {@link DriverAwarenessSupplierService} that this |
| * connection is for |
| */ |
| DriverAwarenessServiceConnection(int priority) { |
| mPriority = priority; |
| } |
| |
| @Override |
| public void onServiceConnected(ComponentName name, IBinder binder) { |
| logd("onServiceConnected, name: " + name + ", binder: " + binder); |
| IDriverAwarenessSupplier service = IDriverAwarenessSupplier.Stub.asInterface( |
| binder); |
| addDriverAwarenessSupplier(name, service, mPriority); |
| try { |
| service.setCallback(new DriverAwarenessSupplierCallback(name)); |
| service.onReady(); |
| } catch (RemoteException e) { |
| Log.e(TAG, "Unable to call onReady on supplier", e); |
| } |
| } |
| |
| @Override |
| public void onServiceDisconnected(ComponentName name) { |
| logd("onServiceDisconnected, name: " + name); |
| removeDriverAwarenessSupplier(name); |
| // TODO(b/146471650) rebind to driver awareness suppliers on service disconnect |
| } |
| } |
| |
| /** |
| * Driver awareness listener that keeps a references to some attributes of the supplier. |
| */ |
| private class DriverAwarenessSupplierCallback extends IDriverAwarenessSupplierCallback.Stub { |
| |
| private final ComponentName mComponentName; |
| |
| /** |
| * Construct an instance of {@link DriverAwarenessSupplierCallback}. |
| * |
| * @param componentName the driver awareness supplier for this listener |
| */ |
| DriverAwarenessSupplierCallback(ComponentName componentName) { |
| mComponentName = componentName; |
| } |
| |
| @Override |
| public void onDriverAwarenessUpdated(DriverAwarenessEvent event) { |
| IDriverAwarenessSupplier supplier; |
| long maxStaleness; |
| synchronized (mLock) { |
| supplier = mSupplierBinders.get(mComponentName); |
| maxStaleness = mSupplierConfigs.get(supplier).getMaxStalenessMillis(); |
| } |
| if (supplier == null) { |
| // this should never happen. Initialization process would not be correct. |
| throw new IllegalStateException( |
| "No supplier registered for component " + mComponentName); |
| } |
| logd(String.format("Driver awareness updated for %s: %s", |
| supplier.getClass().getSimpleName(), event)); |
| handleDriverAwarenessEvent( |
| new DriverAwarenessEventWrapper(event, supplier, maxStaleness)); |
| } |
| |
| @Override |
| public void onConfigLoaded(DriverAwarenessSupplierConfig config) throws RemoteException { |
| synchronized (mLock) { |
| mSupplierConfigs.put(mSupplierBinders.get(mComponentName), config); |
| } |
| } |
| } |
| |
| /** |
| * Wrapper for {@link DriverAwarenessEvent} that includes some information from the supplier |
| * that emitted the event. |
| */ |
| @VisibleForTesting |
| static class DriverAwarenessEventWrapper { |
| final DriverAwarenessEvent mAwarenessEvent; |
| final IDriverAwarenessSupplier mSupplier; |
| final long mMaxStaleness; |
| |
| /** |
| * Construct an instance of {@link DriverAwarenessEventWrapper}. |
| * |
| * @param awarenessEvent the driver awareness event being wrapped |
| * @param supplier the driver awareness supplier for this listener |
| * @param maxStaleness the max staleness of the supplier that emitted this event (included |
| * to avoid making a binder call) |
| */ |
| DriverAwarenessEventWrapper( |
| DriverAwarenessEvent awarenessEvent, |
| IDriverAwarenessSupplier supplier, |
| long maxStaleness) { |
| mAwarenessEvent = awarenessEvent; |
| mSupplier = supplier; |
| mMaxStaleness = maxStaleness; |
| } |
| |
| @Override |
| public String toString() { |
| return String.format( |
| "DriverAwarenessEventWrapper{mAwarenessChangeEvent=%s, mSupplier=%s, " |
| + "mMaxStaleness=%s}", |
| mAwarenessEvent, mSupplier, mMaxStaleness); |
| } |
| } |
| } |