blob: 247cb6c705428b559926eb43325c730eda163a2f [file] [log] [blame]
Ram Periathiruvadi25c16f12017-11-17 16:48:37 -08001/*
2 * Copyright (C) 2018 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
Yao, Yuxing8f475c12019-02-25 16:25:01 -080019import static android.car.drivingstate.CarUxRestrictionsManager.UX_RESTRICTION_MODE_BASELINE;
20
Yao, Yuxing0811d5a2018-11-29 16:18:53 -080021import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
22
Ram Periathiruvadi25c16f12017-11-17 16:48:37 -080023import android.annotation.Nullable;
Yao, Yuxing0811d5a2018-11-29 16:18:53 -080024import android.car.Car;
Ram Periathiruvadi25c16f12017-11-17 16:48:37 -080025import android.car.drivingstate.CarDrivingStateEvent;
26import android.car.drivingstate.CarDrivingStateEvent.CarDrivingState;
Ram Periathiruvadi4526a432018-01-24 13:00:54 -080027import android.car.drivingstate.CarUxRestrictions;
Yao, Yuxing0811d5a2018-11-29 16:18:53 -080028import android.car.drivingstate.CarUxRestrictionsConfiguration;
Yao, Yuxing8f475c12019-02-25 16:25:01 -080029import android.car.drivingstate.CarUxRestrictionsManager;
Ram Periathiruvadi25c16f12017-11-17 16:48:37 -080030import android.car.drivingstate.ICarDrivingStateChangeListener;
Ram Periathiruvadi4526a432018-01-24 13:00:54 -080031import android.car.drivingstate.ICarUxRestrictionsChangeListener;
32import android.car.drivingstate.ICarUxRestrictionsManager;
Steve Paik4d257022018-04-27 13:28:31 -070033import android.car.hardware.CarPropertyValue;
34import android.car.hardware.property.CarPropertyEvent;
35import android.car.hardware.property.ICarPropertyEventListener;
Ram Periathiruvadi25c16f12017-11-17 16:48:37 -080036import android.content.Context;
Ram Periathiruvadicdee1202018-07-17 18:01:31 -070037import android.content.pm.PackageManager;
Steve Paik4d257022018-04-27 13:28:31 -070038import android.hardware.automotive.vehicle.V2_0.VehicleProperty;
Ram Periathiruvadicdee1202018-07-17 18:01:31 -070039import android.os.Binder;
40import android.os.Build;
Ram Periathiruvadi25c16f12017-11-17 16:48:37 -080041import android.os.IBinder;
Ram Periathiruvadicdee1202018-07-17 18:01:31 -070042import android.os.Process;
Ram Periathiruvadi25c16f12017-11-17 16:48:37 -080043import android.os.RemoteException;
Yao, Yuxing0811d5a2018-11-29 16:18:53 -080044import android.os.SystemClock;
Yao, Yuxing0811d5a2018-11-29 16:18:53 -080045import android.util.AtomicFile;
46import android.util.JsonReader;
47import android.util.JsonWriter;
Ram Periathiruvadi25c16f12017-11-17 16:48:37 -080048import android.util.Log;
Yao, Yuxing9bfb7492019-02-15 11:53:34 -080049import android.util.Slog;
Ram Periathiruvadiec551ae2018-03-15 11:38:07 -070050
Yao, Yuxing9bfb7492019-02-15 11:53:34 -080051import com.android.car.systeminterface.SystemInterface;
Ram Periathiruvadicdee1202018-07-17 18:01:31 -070052import com.android.internal.annotations.GuardedBy;
Yao, Yuxing0811d5a2018-11-29 16:18:53 -080053import com.android.internal.annotations.VisibleForTesting;
Ram Periathiruvadicdee1202018-07-17 18:01:31 -070054
Ram Periathiruvadiec551ae2018-03-15 11:38:07 -070055import org.xmlpull.v1.XmlPullParserException;
56
Yao, Yuxing0811d5a2018-11-29 16:18:53 -080057import java.io.File;
58import java.io.FileOutputStream;
Ram Periathiruvadied9e8f82018-03-07 11:50:49 -080059import java.io.IOException;
Yao, Yuxing0811d5a2018-11-29 16:18:53 -080060import java.io.InputStreamReader;
61import java.io.OutputStreamWriter;
Ram Periathiruvadi25c16f12017-11-17 16:48:37 -080062import java.io.PrintWriter;
Yao, Yuxing0811d5a2018-11-29 16:18:53 -080063import java.nio.charset.StandardCharsets;
64import java.nio.file.Files;
65import java.nio.file.Path;
Ram Periathiruvadi25c16f12017-11-17 16:48:37 -080066import java.util.ArrayList;
Ram Periathiruvadi22c0d682018-05-10 17:50:48 -070067import java.util.LinkedList;
Ram Periathiruvadi25c16f12017-11-17 16:48:37 -080068import java.util.List;
69
70/**
71 * A service that listens to current driving state of the vehicle and maps it to the
72 * appropriate UX restrictions for that driving state.
Yao, Yuxing0811d5a2018-11-29 16:18:53 -080073 * <p>
74 * <h1>UX Restrictions Configuration</h1>
75 * When this service starts, it will first try reading the configuration set through
76 * {@link #saveUxRestrictionsConfigurationForNextBoot(CarUxRestrictionsConfiguration)}.
77 * If one is not available, it will try reading the configuration saved in
78 * {@code R.xml.car_ux_restrictions_map}. If XML is somehow unavailable, it will
79 * fall back to a hard-coded configuration.
Ram Periathiruvadi25c16f12017-11-17 16:48:37 -080080 */
Ram Periathiruvadi4526a432018-01-24 13:00:54 -080081public class CarUxRestrictionsManagerService extends ICarUxRestrictionsManager.Stub implements
82 CarServiceBase {
83 private static final String TAG = "CarUxR";
Ram Periathiruvadi25c16f12017-11-17 16:48:37 -080084 private static final boolean DBG = false;
Ram Periathiruvadi22c0d682018-05-10 17:50:48 -070085 private static final int MAX_TRANSITION_LOG_SIZE = 20;
Steve Paik4d257022018-04-27 13:28:31 -070086 private static final int PROPERTY_UPDATE_RATE = 5; // Update rate in Hz
Ram Periathiruvadif6381fb2018-06-05 18:44:19 -070087 private static final float SPEED_NOT_AVAILABLE = -1.0F;
Yao, Yuxing0811d5a2018-11-29 16:18:53 -080088
89 @VisibleForTesting
Yao, Yuxing9bfb7492019-02-15 11:53:34 -080090 /* package */ static final String CONFIG_FILENAME_PRODUCTION =
91 "ux_restrictions_prod_config.json";
Yao, Yuxing0811d5a2018-11-29 16:18:53 -080092 @VisibleForTesting
Yao, Yuxing9bfb7492019-02-15 11:53:34 -080093 /* package */ static final String CONFIG_FILENAME_STAGED =
94 "ux_restrictions_staged_config.json";
Yao, Yuxing0811d5a2018-11-29 16:18:53 -080095
Ram Periathiruvadi25c16f12017-11-17 16:48:37 -080096 private final Context mContext;
97 private final CarDrivingStateService mDrivingStateService;
Steve Paik4d257022018-04-27 13:28:31 -070098 private final CarPropertyService mCarPropertyService;
Ram Periathiruvadi25c16f12017-11-17 16:48:37 -080099 // List of clients listening to UX restriction events.
Ram Periathiruvadi4526a432018-01-24 13:00:54 -0800100 private final List<UxRestrictionsClient> mUxRClients = new ArrayList<>();
Yao, Yuxing8f475c12019-02-25 16:25:01 -0800101 @VisibleForTesting
102 CarUxRestrictionsConfiguration mCarUxRestrictionsConfiguration;
103 @CarUxRestrictionsManager.UxRestrictionMode
104 private int mRestrictionMode = UX_RESTRICTION_MODE_BASELINE;
Ram Periathiruvadi4526a432018-01-24 13:00:54 -0800105 private CarUxRestrictions mCurrentUxRestrictions;
Ram Periathiruvadied9e8f82018-03-07 11:50:49 -0800106 private float mCurrentMovingSpeed;
Ram Periathiruvadicdee1202018-07-17 18:01:31 -0700107 // Flag to disable broadcasting UXR changes - for development purposes
108 @GuardedBy("this")
109 private boolean mUxRChangeBroadcastEnabled = true;
Ram Periathiruvadi22c0d682018-05-10 17:50:48 -0700110 // For dumpsys logging
111 private final LinkedList<Utils.TransitionLog> mTransitionLogs = new LinkedList<>();
112
Ram Periathiruvadied9e8f82018-03-07 11:50:49 -0800113 public CarUxRestrictionsManagerService(Context context, CarDrivingStateService drvService,
Yao, Yuxing9bfb7492019-02-15 11:53:34 -0800114 CarPropertyService propertyService) {
Ram Periathiruvadi25c16f12017-11-17 16:48:37 -0800115 mContext = context;
116 mDrivingStateService = drvService;
Steve Paik4d257022018-04-27 13:28:31 -0700117 mCarPropertyService = propertyService;
Yao, Yuxing9bfb7492019-02-15 11:53:34 -0800118 // Dir for config files are not available at this point. Read from XML.
119 // If prod config is set, it will be loaded during init().
120 mCarUxRestrictionsConfiguration = readXmlConfig();
Ram Periathiruvadi12f42432018-03-28 19:47:37 -0700121 // Unrestricted until driving state information is received. During boot up, we don't want
Steve Paik4d257022018-04-27 13:28:31 -0700122 // everything to be blocked until data is available from CarPropertyManager. If we start
Ram Periathiruvadi12f42432018-03-28 19:47:37 -0700123 // driving and we don't get speed or gear information, we have bigger problems.
Yao, Yuxing0811d5a2018-11-29 16:18:53 -0800124 mCurrentUxRestrictions = new CarUxRestrictions.Builder(/* reqOpt= */ false,
125 CarUxRestrictions.UX_RESTRICTIONS_BASELINE, SystemClock.elapsedRealtimeNanos())
126 .build();
Ram Periathiruvadi25c16f12017-11-17 16:48:37 -0800127 }
128
129 @Override
Ram Periathiruvadif6381fb2018-06-05 18:44:19 -0700130 public synchronized void init() {
Ram Periathiruvadi25c16f12017-11-17 16:48:37 -0800131 // subscribe to driving State
132 mDrivingStateService.registerDrivingStateChangeListener(
133 mICarDrivingStateChangeEventListener);
Steve Paik4d257022018-04-27 13:28:31 -0700134 // subscribe to property service for speed
135 mCarPropertyService.registerListener(VehicleProperty.PERF_VEHICLE_SPEED,
136 PROPERTY_UPDATE_RATE, mICarPropertyEventListener);
Yao, Yuxing9bfb7492019-02-15 11:53:34 -0800137 // Load config again after car driving state service inits. At this stage the driving
138 // state is known, which determines whether it's safe to load new config.
139 mCarUxRestrictionsConfiguration = loadConfig();
Ram Periathiruvadif6381fb2018-06-05 18:44:19 -0700140 initializeUxRestrictions();
141 }
142
Wenting Zhai29021312019-01-09 12:11:14 -0800143 @Override
144 public CarUxRestrictionsConfiguration getConfig() {
Yao, Yuxing0811d5a2018-11-29 16:18:53 -0800145 return mCarUxRestrictionsConfiguration;
146 }
147
148 /**
149 * Loads a UX restrictions configuration and returns it.
Yao, Yuxing9bfb7492019-02-15 11:53:34 -0800150 *
Yao, Yuxing0811d5a2018-11-29 16:18:53 -0800151 * <p>Reads config from the following sources in order:
152 * <ol>
153 * <li>saved config set by
154 * {@link #saveUxRestrictionsConfigurationForNextBoot(CarUxRestrictionsConfiguration)};
155 * <li>XML resource config from {@code R.xml.car_ux_restrictions_map};
156 * <li>hardcoded default config.
157 * </ol>
Yao, Yuxing9bfb7492019-02-15 11:53:34 -0800158 *
159 * This method attempts to promote staged config file. Doing which depends on driving state.
Yao, Yuxing0811d5a2018-11-29 16:18:53 -0800160 */
161 @VisibleForTesting
162 /* package */ synchronized CarUxRestrictionsConfiguration loadConfig() {
163 promoteStagedConfig();
164
Yao, Yuxing9bfb7492019-02-15 11:53:34 -0800165 CarUxRestrictionsConfiguration config;
Yao, Yuxing0811d5a2018-11-29 16:18:53 -0800166 // Production config, if available, is the first choice.
Yao, Yuxing9bfb7492019-02-15 11:53:34 -0800167 File prodConfig = getFile(CONFIG_FILENAME_PRODUCTION);
Yao, Yuxing0811d5a2018-11-29 16:18:53 -0800168 if (prodConfig.exists()) {
169 logd("Attempting to read production config");
170 config = readPersistedConfig(prodConfig);
171 if (config != null) {
172 return config;
173 }
174 }
175
176 // XML config is the second choice.
177 logd("Attempting to read config from XML resource");
178 config = readXmlConfig();
179 if (config != null) {
180 return config;
181 }
182
183 // This should rarely happen.
184 Log.w(TAG, "Creating default config");
185 return createDefaultConfig();
186 }
187
Yao, Yuxing9bfb7492019-02-15 11:53:34 -0800188 private File getFile(String filename) {
189 SystemInterface systemInterface = CarLocalServices.getService(SystemInterface.class);
190 return new File(systemInterface.getSystemCarDir(), filename);
191 }
192
Yao, Yuxing0811d5a2018-11-29 16:18:53 -0800193 @Nullable
194 private CarUxRestrictionsConfiguration readXmlConfig() {
195 try {
196 return CarUxRestrictionsConfigurationXmlParser.parse(mContext,
197 R.xml.car_ux_restrictions_map);
198 } catch (IOException | XmlPullParserException e) {
199 Log.e(TAG, "Could not read config from XML resource", e);
200 }
201 return null;
202 }
203
204 private void promoteStagedConfig() {
Yao, Yuxing9bfb7492019-02-15 11:53:34 -0800205 Path stagedConfig = getFile(CONFIG_FILENAME_STAGED).toPath();
Yao, Yuxing0811d5a2018-11-29 16:18:53 -0800206
207 CarDrivingStateEvent currentDrivingStateEvent =
208 mDrivingStateService.getCurrentDrivingState();
209 // Only promote staged config when car is parked.
210 if (currentDrivingStateEvent != null
211 && currentDrivingStateEvent.eventValue == CarDrivingStateEvent.DRIVING_STATE_PARKED
212 && Files.exists(stagedConfig)) {
Yao, Yuxing9bfb7492019-02-15 11:53:34 -0800213
214 Path prod = getFile(CONFIG_FILENAME_PRODUCTION).toPath();
Yao, Yuxing0811d5a2018-11-29 16:18:53 -0800215 try {
216 logd("Attempting to promote stage config");
217 Files.move(stagedConfig, prod, REPLACE_EXISTING);
218 } catch (IOException e) {
219 Log.e(TAG, "Could not promote state config", e);
220 }
221 }
222 }
223
Ram Periathiruvadif6381fb2018-06-05 18:44:19 -0700224 // Update current restrictions by getting the current driving state and speed.
225 private void initializeUxRestrictions() {
226 CarDrivingStateEvent currentDrivingStateEvent =
227 mDrivingStateService.getCurrentDrivingState();
228 // if we don't have enough information from the CarPropertyService to compute the UX
229 // restrictions, then leave the UX restrictions unchanged from what it was initialized to
230 // in the constructor.
231 if (currentDrivingStateEvent == null || currentDrivingStateEvent.eventValue
232 == CarDrivingStateEvent.DRIVING_STATE_UNKNOWN) {
233 return;
234 }
235 int currentDrivingState = currentDrivingStateEvent.eventValue;
236 Float currentSpeed = getCurrentSpeed();
237 if (currentSpeed == SPEED_NOT_AVAILABLE) {
238 return;
239 }
240 // At this point the underlying CarPropertyService has provided us enough information to
241 // compute the UX restrictions that could be potentially different from the initial UX
242 // restrictions.
243 handleDispatchUxRestrictions(currentDrivingState, currentSpeed);
244 }
245
246 private Float getCurrentSpeed() {
247 CarPropertyValue value = mCarPropertyService.getProperty(VehicleProperty.PERF_VEHICLE_SPEED,
248 0);
249 if (value != null) {
250 return (Float) value.getValue();
251 }
252 return SPEED_NOT_AVAILABLE;
Ram Periathiruvadi25c16f12017-11-17 16:48:37 -0800253 }
254
255 @Override
256 public synchronized void release() {
Ram Periathiruvadi4526a432018-01-24 13:00:54 -0800257 for (UxRestrictionsClient client : mUxRClients) {
Ram Periathiruvadi25c16f12017-11-17 16:48:37 -0800258 client.listenerBinder.unlinkToDeath(client, 0);
259 }
Ram Periathiruvadi4526a432018-01-24 13:00:54 -0800260 mUxRClients.clear();
Ram Periathiruvadi25c16f12017-11-17 16:48:37 -0800261 mDrivingStateService.unregisterDrivingStateChangeListener(
262 mICarDrivingStateChangeEventListener);
263 }
264
265 // Binder methods
266
267 /**
Ram Periathiruvadi4526a432018-01-24 13:00:54 -0800268 * Register a {@link ICarUxRestrictionsChangeListener} to be notified for changes to the UX
Ram Periathiruvadi25c16f12017-11-17 16:48:37 -0800269 * restrictions
270 *
271 * @param listener listener to register
272 */
273 @Override
Ram Periathiruvadi4526a432018-01-24 13:00:54 -0800274 public synchronized void registerUxRestrictionsChangeListener(
275 ICarUxRestrictionsChangeListener listener) {
Ram Periathiruvadi25c16f12017-11-17 16:48:37 -0800276 if (listener == null) {
Yao, Yuxing0811d5a2018-11-29 16:18:53 -0800277 Log.e(TAG, "registerUxRestrictionsChangeListener(): listener null");
Ram Periathiruvadi25c16f12017-11-17 16:48:37 -0800278 throw new IllegalArgumentException("Listener is null");
279 }
280 // If a new client is registering, create a new DrivingStateClient and add it to the list
281 // of listening clients.
Ram Periathiruvadi4526a432018-01-24 13:00:54 -0800282 UxRestrictionsClient client = findUxRestrictionsClient(listener);
Ram Periathiruvadi25c16f12017-11-17 16:48:37 -0800283 if (client == null) {
Ram Periathiruvadi4526a432018-01-24 13:00:54 -0800284 client = new UxRestrictionsClient(listener);
Ram Periathiruvadi25c16f12017-11-17 16:48:37 -0800285 try {
286 listener.asBinder().linkToDeath(client, 0);
287 } catch (RemoteException e) {
288 Log.e(TAG, "Cannot link death recipient to binder " + e);
289 }
Ram Periathiruvadi4526a432018-01-24 13:00:54 -0800290 mUxRClients.add(client);
Ram Periathiruvadi25c16f12017-11-17 16:48:37 -0800291 }
292 return;
293 }
294
295 /**
296 * Iterates through the list of registered UX Restrictions clients -
Ram Periathiruvadi4526a432018-01-24 13:00:54 -0800297 * {@link UxRestrictionsClient} and finds if the given client is already registered.
Ram Periathiruvadi25c16f12017-11-17 16:48:37 -0800298 *
299 * @param listener Listener to look for.
Ram Periathiruvadi4526a432018-01-24 13:00:54 -0800300 * @return the {@link UxRestrictionsClient} if found, null if not
Ram Periathiruvadi25c16f12017-11-17 16:48:37 -0800301 */
302 @Nullable
Ram Periathiruvadi4526a432018-01-24 13:00:54 -0800303 private UxRestrictionsClient findUxRestrictionsClient(
304 ICarUxRestrictionsChangeListener listener) {
Ram Periathiruvadi25c16f12017-11-17 16:48:37 -0800305 IBinder binder = listener.asBinder();
Ram Periathiruvadi4526a432018-01-24 13:00:54 -0800306 for (UxRestrictionsClient client : mUxRClients) {
Ram Periathiruvadi25c16f12017-11-17 16:48:37 -0800307 if (client.isHoldingBinder(binder)) {
308 return client;
309 }
310 }
311 return null;
312 }
313
Ram Periathiruvadi25c16f12017-11-17 16:48:37 -0800314 /**
315 * Unregister the given UX Restrictions listener
316 *
317 * @param listener client to unregister
318 */
319 @Override
Ram Periathiruvadi4526a432018-01-24 13:00:54 -0800320 public synchronized void unregisterUxRestrictionsChangeListener(
321 ICarUxRestrictionsChangeListener listener) {
Ram Periathiruvadi25c16f12017-11-17 16:48:37 -0800322 if (listener == null) {
Ram Periathiruvadi4526a432018-01-24 13:00:54 -0800323 Log.e(TAG, "unregisterUxRestrictionsChangeListener(): listener null");
Ram Periathiruvadi25c16f12017-11-17 16:48:37 -0800324 throw new IllegalArgumentException("Listener is null");
325 }
326
Ram Periathiruvadi4526a432018-01-24 13:00:54 -0800327 UxRestrictionsClient client = findUxRestrictionsClient(listener);
Ram Periathiruvadi25c16f12017-11-17 16:48:37 -0800328 if (client == null) {
Ram Periathiruvadi4526a432018-01-24 13:00:54 -0800329 Log.e(TAG, "unregisterUxRestrictionsChangeListener(): listener was not previously "
Ram Periathiruvadi25c16f12017-11-17 16:48:37 -0800330 + "registered");
331 return;
332 }
333 listener.asBinder().unlinkToDeath(client, 0);
Ram Periathiruvadi4526a432018-01-24 13:00:54 -0800334 mUxRClients.remove(client);
Ram Periathiruvadi25c16f12017-11-17 16:48:37 -0800335 }
336
337 /**
338 * Gets the current UX restrictions
339 *
Ram Periathiruvadi4526a432018-01-24 13:00:54 -0800340 * @return {@link CarUxRestrictions} for the given event type
Ram Periathiruvadi25c16f12017-11-17 16:48:37 -0800341 */
342 @Override
343 @Nullable
Ram Periathiruvadi4526a432018-01-24 13:00:54 -0800344 public synchronized CarUxRestrictions getCurrentUxRestrictions() {
345 return mCurrentUxRestrictions;
Ram Periathiruvadi25c16f12017-11-17 16:48:37 -0800346 }
347
Yao, Yuxing0811d5a2018-11-29 16:18:53 -0800348 @Override
349 public synchronized boolean saveUxRestrictionsConfigurationForNextBoot(
350 CarUxRestrictionsConfiguration config) {
351 ICarImpl.assertPermission(mContext, Car.PERMISSION_CAR_UX_RESTRICTIONS_CONFIGURATION);
352 return persistConfig(config, CONFIG_FILENAME_STAGED);
353 }
354
Wenting Zhai29021312019-01-09 12:11:14 -0800355 @Override
356 @Nullable
357 public CarUxRestrictionsConfiguration getStagedConfig() {
Yao, Yuxing9bfb7492019-02-15 11:53:34 -0800358 File stagedConfig = getFile(CONFIG_FILENAME_STAGED);
Wenting Zhai29021312019-01-09 12:11:14 -0800359 if (stagedConfig.exists()) {
360 logd("Attempting to read staged config");
361 return readPersistedConfig(stagedConfig);
362 } else {
363 return null;
364 }
365 }
366
Yao, Yuxing0811d5a2018-11-29 16:18:53 -0800367 /**
Yao, Yuxing8f475c12019-02-25 16:25:01 -0800368 * Sets the restriction mode to use. Restriction mode allows a different set of restrictions to
369 * be applied in the same driving state. Restrictions for each mode can be configured through
370 * {@link CarUxRestrictionsConfiguration}.
371 *
372 * <p>Defaults to {@link CarUxRestrictionsManager#UX_RESTRICTION_MODE_BASELINE}.
373 *
374 * @param mode See values in {@link CarUxRestrictionsManager.UxRestrictionMode}.
375 * @return {@code true} if mode was successfully changed; {@code false} otherwise.
376 *
377 * @see CarUxRestrictionsConfiguration.DrivingStateRestrictions
378 * @see CarUxRestrictionsConfiguration.Builder
379 */
380 @Override
381 public synchronized boolean setRestrictionMode(
382 @CarUxRestrictionsManager.UxRestrictionMode int mode) {
383 if (mRestrictionMode == mode) {
384 return true;
385 }
386
387 addTransitionLog(TAG, mRestrictionMode, mode, System.currentTimeMillis(),
388 "Restriction mode");
389 mRestrictionMode = mode;
390 logd("Set restriction mode to: " + CarUxRestrictionsManager.modeToString(mode));
391
392 handleDispatchUxRestrictions(
393 mDrivingStateService.getCurrentDrivingState().eventValue, getCurrentSpeed());
394 return true;
395 }
396
397 @Override
398 @CarUxRestrictionsManager.UxRestrictionMode
399 public synchronized int getRestrictionMode() {
400 return mRestrictionMode;
401 }
402
403 /**
Yao, Yuxing0811d5a2018-11-29 16:18:53 -0800404 * Writes configuration into the specified file.
405 *
406 * IO access on file is not thread safe. Caller should ensure threading protection.
407 */
408 private boolean persistConfig(CarUxRestrictionsConfiguration config, String filename) {
Yao, Yuxing9bfb7492019-02-15 11:53:34 -0800409 File file = getFile(filename);
410 AtomicFile stagedFile = new AtomicFile(file);
Yao, Yuxing0811d5a2018-11-29 16:18:53 -0800411 FileOutputStream fos;
412 try {
413 fos = stagedFile.startWrite();
414 } catch (IOException e) {
415 Log.e(TAG, "Could not open file to persist config", e);
416 return false;
417 }
418 try (JsonWriter jsonWriter = new JsonWriter(
419 new OutputStreamWriter(fos, StandardCharsets.UTF_8))) {
420 config.writeJson(jsonWriter);
421 } catch (IOException e) {
422 Log.e(TAG, "Could not persist config", e);
423 stagedFile.failWrite(fos);
424 return false;
425 }
426 stagedFile.finishWrite(fos);
427 return true;
428 }
429
430 @Nullable
431 private CarUxRestrictionsConfiguration readPersistedConfig(File file) {
432 if (!file.exists()) {
433 Log.e(TAG, "Could not find config file: " + file.getName());
434 return null;
435 }
436
437 AtomicFile config = new AtomicFile(file);
438 try (JsonReader reader = new JsonReader(
439 new InputStreamReader(config.openRead(), StandardCharsets.UTF_8))) {
440 return CarUxRestrictionsConfiguration.readJson(reader);
441 } catch (IOException e) {
442 Log.e(TAG, "Could not read persisted config file " + file.getName(), e);
443 }
444 return null;
445 }
446
Ram Periathiruvadi25c16f12017-11-17 16:48:37 -0800447 /**
Ram Periathiruvadicdee1202018-07-17 18:01:31 -0700448 * Enable/disable UX restrictions change broadcast blocking.
449 * Setting this to true will stop broadcasts of UX restriction change to listeners.
450 * This method works only on debug builds and the caller of this method needs to have the same
451 * signature of the car service.
452 *
453 */
454 public synchronized void setUxRChangeBroadcastEnabled(boolean enable) {
455 if (!isDebugBuild()) {
456 Log.e(TAG, "Cannot set UX restriction change broadcast.");
457 return;
458 }
459 // Check if the caller has the same signature as that of the car service.
460 if (mContext.getPackageManager().checkSignatures(Process.myUid(), Binder.getCallingUid())
461 != PackageManager.SIGNATURE_MATCH) {
462 throw new SecurityException(
463 "Caller " + mContext.getPackageManager().getNameForUid(Binder.getCallingUid())
464 + " does not have the right signature");
465 }
466 if (enable) {
467 // if enabling it back, send the current restrictions
468 mUxRChangeBroadcastEnabled = enable;
469 handleDispatchUxRestrictions(mDrivingStateService.getCurrentDrivingState().eventValue,
470 getCurrentSpeed());
471 } else {
472 // fake parked state, so if the system is currently restricted, the restrictions are
473 // relaxed.
474 handleDispatchUxRestrictions(CarDrivingStateEvent.DRIVING_STATE_PARKED, 0);
475 mUxRChangeBroadcastEnabled = enable;
476 }
477 }
478
479 private boolean isDebugBuild() {
480 return Build.IS_USERDEBUG || Build.IS_ENG;
481 }
482
483 /**
Ram Periathiruvadi25c16f12017-11-17 16:48:37 -0800484 * Class that holds onto client related information - listener interface, process that hosts the
485 * binder object etc.
486 * It also registers for death notifications of the host.
487 */
Ram Periathiruvadi4526a432018-01-24 13:00:54 -0800488 private class UxRestrictionsClient implements IBinder.DeathRecipient {
Ram Periathiruvadi25c16f12017-11-17 16:48:37 -0800489 private final IBinder listenerBinder;
Ram Periathiruvadi4526a432018-01-24 13:00:54 -0800490 private final ICarUxRestrictionsChangeListener listener;
Ram Periathiruvadi25c16f12017-11-17 16:48:37 -0800491
Ram Periathiruvadi4526a432018-01-24 13:00:54 -0800492 public UxRestrictionsClient(ICarUxRestrictionsChangeListener l) {
Ram Periathiruvadi25c16f12017-11-17 16:48:37 -0800493 listener = l;
494 listenerBinder = l.asBinder();
495 }
496
497 @Override
498 public void binderDied() {
Yao, Yuxing0811d5a2018-11-29 16:18:53 -0800499 logd("Binder died " + listenerBinder);
Ram Periathiruvadi25c16f12017-11-17 16:48:37 -0800500 listenerBinder.unlinkToDeath(this, 0);
Ram Periathiruvadi4526a432018-01-24 13:00:54 -0800501 synchronized (CarUxRestrictionsManagerService.this) {
502 mUxRClients.remove(this);
Ram Periathiruvadi25c16f12017-11-17 16:48:37 -0800503 }
504 }
505
506 /**
507 * Returns if the given binder object matches to what this client info holds.
508 * Used to check if the listener asking to be registered is already registered.
509 *
510 * @return true if matches, false if not
511 */
512 public boolean isHoldingBinder(IBinder binder) {
513 return listenerBinder == binder;
514 }
515
516 /**
517 * Dispatch the event to the listener
518 *
Ram Periathiruvadi4526a432018-01-24 13:00:54 -0800519 * @param event {@link CarUxRestrictions}.
Ram Periathiruvadi25c16f12017-11-17 16:48:37 -0800520 */
Ram Periathiruvadi4526a432018-01-24 13:00:54 -0800521 public void dispatchEventToClients(CarUxRestrictions event) {
Ram Periathiruvadi25c16f12017-11-17 16:48:37 -0800522 if (event == null) {
523 return;
524 }
525 try {
Ram Periathiruvadi4526a432018-01-24 13:00:54 -0800526 listener.onUxRestrictionsChanged(event);
Ram Periathiruvadi25c16f12017-11-17 16:48:37 -0800527 } catch (RemoteException e) {
Yao, Yuxing0811d5a2018-11-29 16:18:53 -0800528 Log.e(TAG, "Dispatch to listener failed", e);
Ram Periathiruvadi25c16f12017-11-17 16:48:37 -0800529 }
530 }
531 }
532
533 @Override
534 public void dump(PrintWriter writer) {
Ram Periathiruvadi12f42432018-03-28 19:47:37 -0700535 writer.println(
536 "Requires DO? " + mCurrentUxRestrictions.isRequiresDistractionOptimization());
537 writer.println("Current UXR: " + mCurrentUxRestrictions.getActiveRestrictions());
Ram Periathiruvadicdee1202018-07-17 18:01:31 -0700538 if (isDebugBuild()) {
539 writer.println("mUxRChangeBroadcastEnabled? " + mUxRChangeBroadcastEnabled);
540 }
Yao, Yuxing0811d5a2018-11-29 16:18:53 -0800541 mCarUxRestrictionsConfiguration.dump(writer);
Ram Periathiruvadi22c0d682018-05-10 17:50:48 -0700542 writer.println("UX Restriction change log:");
543 for (Utils.TransitionLog tlog : mTransitionLogs) {
544 writer.println(tlog);
545 }
Ram Periathiruvadi25c16f12017-11-17 16:48:37 -0800546 }
547
548 /**
549 * {@link CarDrivingStateEvent} listener registered with the {@link CarDrivingStateService}
550 * for getting driving state change notifications.
551 */
552 private final ICarDrivingStateChangeListener mICarDrivingStateChangeEventListener =
553 new ICarDrivingStateChangeListener.Stub() {
554 @Override
555 public void onDrivingStateChanged(CarDrivingStateEvent event) {
Yao, Yuxing0811d5a2018-11-29 16:18:53 -0800556 logd("Driving State Changed:" + event.eventValue);
Ram Periathiruvadi25c16f12017-11-17 16:48:37 -0800557 handleDrivingStateEvent(event);
558 }
559 };
560
561 /**
562 * Handle the driving state change events coming from the {@link CarDrivingStateService}.
563 * Map the driving state to the corresponding UX Restrictions and dispatch the
564 * UX Restriction change to the registered clients.
565 */
566 private synchronized void handleDrivingStateEvent(CarDrivingStateEvent event) {
567 if (event == null) {
568 return;
569 }
570 int drivingState = event.eventValue;
Ram Periathiruvadif6381fb2018-06-05 18:44:19 -0700571 Float speed = getCurrentSpeed();
572
573 if (speed != SPEED_NOT_AVAILABLE) {
574 mCurrentMovingSpeed = speed;
Ram Periathiruvadiec551ae2018-03-15 11:38:07 -0700575 } else if (drivingState == CarDrivingStateEvent.DRIVING_STATE_PARKED
576 || drivingState == CarDrivingStateEvent.DRIVING_STATE_UNKNOWN) {
577 // If speed is unavailable, but the driving state is parked or unknown, it can still be
578 // handled.
Yao, Yuxing0811d5a2018-11-29 16:18:53 -0800579 logd("Speed null when driving state is: " + drivingState);
Ram Periathiruvadiec551ae2018-03-15 11:38:07 -0700580 mCurrentMovingSpeed = 0;
581 } else {
582 // If we get here with driving state != parked or unknown && speed == null,
583 // something is wrong. CarDrivingStateService could not have inferred idling or moving
584 // when speed is not available
585 Log.e(TAG, "Unexpected: Speed null when driving state is: " + drivingState);
586 return;
587 }
588 handleDispatchUxRestrictions(drivingState, mCurrentMovingSpeed);
Ram Periathiruvadied9e8f82018-03-07 11:50:49 -0800589 }
590
591 /**
Steve Paik4d257022018-04-27 13:28:31 -0700592 * {@link CarPropertyEvent} listener registered with the {@link CarPropertyService} for getting
Ram Periathiruvadied9e8f82018-03-07 11:50:49 -0800593 * speed change notifications.
594 */
Steve Paik4d257022018-04-27 13:28:31 -0700595 private final ICarPropertyEventListener mICarPropertyEventListener =
596 new ICarPropertyEventListener.Stub() {
Ram Periathiruvadied9e8f82018-03-07 11:50:49 -0800597 @Override
Steve Paik4d257022018-04-27 13:28:31 -0700598 public void onEvent(List<CarPropertyEvent> events) throws RemoteException {
599 for (CarPropertyEvent event : events) {
600 if ((event.getEventType()
601 == CarPropertyEvent.PROPERTY_EVENT_PROPERTY_CHANGE)
602 && (event.getCarPropertyValue().getPropertyId()
603 == VehicleProperty.PERF_VEHICLE_SPEED)) {
604 handleSpeedChange((Float) event.getCarPropertyValue().getValue());
Ram Periathiruvadied9e8f82018-03-07 11:50:49 -0800605 }
606 }
607 }
608 };
609
610 private synchronized void handleSpeedChange(float newSpeed) {
611 if (newSpeed == mCurrentMovingSpeed) {
612 // Ignore if speed hasn't changed
613 return;
614 }
615 int currentDrivingState = mDrivingStateService.getCurrentDrivingState().eventValue;
616 if (currentDrivingState != CarDrivingStateEvent.DRIVING_STATE_MOVING) {
617 // Ignore speed changes if the vehicle is not moving
618 return;
619 }
620 mCurrentMovingSpeed = newSpeed;
621 handleDispatchUxRestrictions(currentDrivingState, newSpeed);
622 }
623
624 /**
625 * Handle dispatching UX restrictions change.
626 *
627 * @param currentDrivingState driving state of the vehicle
628 * @param speed speed of the vehicle
629 */
630 private synchronized void handleDispatchUxRestrictions(@CarDrivingState int currentDrivingState,
631 float speed) {
Ram Periathiruvadicdee1202018-07-17 18:01:31 -0700632 if (isDebugBuild() && !mUxRChangeBroadcastEnabled) {
633 Log.d(TAG, "Not dispatching UX Restriction due to setting");
634 return;
635 }
636
Yao, Yuxing8f475c12019-02-25 16:25:01 -0800637 CarUxRestrictions uxRestrictions = mCarUxRestrictionsConfiguration.getUxRestrictions(
638 currentDrivingState, speed, mRestrictionMode);
Ram Periathiruvadied9e8f82018-03-07 11:50:49 -0800639
Ram Periathiruvadi25c16f12017-11-17 16:48:37 -0800640 if (DBG) {
Ram Periathiruvadi12f42432018-03-28 19:47:37 -0700641 Log.d(TAG, String.format("DO old->new: %b -> %b",
642 mCurrentUxRestrictions.isRequiresDistractionOptimization(),
643 uxRestrictions.isRequiresDistractionOptimization()));
644 Log.d(TAG, String.format("UxR old->new: 0x%x -> 0x%x",
645 mCurrentUxRestrictions.getActiveRestrictions(),
646 uxRestrictions.getActiveRestrictions()));
Ram Periathiruvadi25c16f12017-11-17 16:48:37 -0800647 }
Ram Periathiruvadi4526a432018-01-24 13:00:54 -0800648
Ram Periathiruvadi12f42432018-03-28 19:47:37 -0700649 if (mCurrentUxRestrictions.isSameRestrictions(uxRestrictions)) {
Ram Periathiruvadied9e8f82018-03-07 11:50:49 -0800650 // Ignore dispatching if the restrictions has not changed.
651 return;
652 }
Ram Periathiruvadi22c0d682018-05-10 17:50:48 -0700653 // for dumpsys logging
654 StringBuilder extraInfo = new StringBuilder();
655 extraInfo.append(
656 mCurrentUxRestrictions.isRequiresDistractionOptimization() ? "DO -> "
657 : "No DO -> ");
658 extraInfo.append(
659 uxRestrictions.isRequiresDistractionOptimization() ? "DO" : "No DO");
660 addTransitionLog(TAG, mCurrentUxRestrictions.getActiveRestrictions(),
661 uxRestrictions.getActiveRestrictions(), System.currentTimeMillis(),
662 extraInfo.toString());
663
Ram Periathiruvadi12f42432018-03-28 19:47:37 -0700664 mCurrentUxRestrictions = uxRestrictions;
Yao, Yuxing0811d5a2018-11-29 16:18:53 -0800665 logd("dispatching to " + mUxRClients.size() + " clients");
Ram Periathiruvadied9e8f82018-03-07 11:50:49 -0800666 for (UxRestrictionsClient client : mUxRClients) {
Ram Periathiruvadi12f42432018-03-28 19:47:37 -0700667 client.dispatchEventToClients(uxRestrictions);
Ram Periathiruvadi25c16f12017-11-17 16:48:37 -0800668 }
669 }
670
Yao, Yuxing0811d5a2018-11-29 16:18:53 -0800671 CarUxRestrictionsConfiguration createDefaultConfig() {
672 return new CarUxRestrictionsConfiguration.Builder()
673 .setUxRestrictions(CarDrivingStateEvent.DRIVING_STATE_PARKED,
Wenting Zhai29021312019-01-09 12:11:14 -0800674 false, CarUxRestrictions.UX_RESTRICTIONS_BASELINE)
Yao, Yuxing0811d5a2018-11-29 16:18:53 -0800675 .setUxRestrictions(CarDrivingStateEvent.DRIVING_STATE_IDLING,
Wenting Zhai29021312019-01-09 12:11:14 -0800676 false, CarUxRestrictions.UX_RESTRICTIONS_BASELINE)
Yao, Yuxing0811d5a2018-11-29 16:18:53 -0800677 .setUxRestrictions(CarDrivingStateEvent.DRIVING_STATE_MOVING,
Wenting Zhai29021312019-01-09 12:11:14 -0800678 true, CarUxRestrictions.UX_RESTRICTIONS_FULLY_RESTRICTED)
Yao, Yuxing0811d5a2018-11-29 16:18:53 -0800679 .setUxRestrictions(CarDrivingStateEvent.DRIVING_STATE_UNKNOWN,
Wenting Zhai29021312019-01-09 12:11:14 -0800680 true, CarUxRestrictions.UX_RESTRICTIONS_FULLY_RESTRICTED)
Yao, Yuxing0811d5a2018-11-29 16:18:53 -0800681 .build();
Ram Periathiruvadi25c16f12017-11-17 16:48:37 -0800682 }
Ram Periathiruvadi22c0d682018-05-10 17:50:48 -0700683
684 private void addTransitionLog(String name, int from, int to, long timestamp, String extra) {
685 if (mTransitionLogs.size() >= MAX_TRANSITION_LOG_SIZE) {
686 mTransitionLogs.remove();
687 }
688
689 Utils.TransitionLog tLog = new Utils.TransitionLog(name, from, to, timestamp, extra);
690 mTransitionLogs.add(tLog);
691 }
692
Yao, Yuxing0811d5a2018-11-29 16:18:53 -0800693 private static void logd(String msg) {
694 if (DBG) {
Yao, Yuxing9bfb7492019-02-15 11:53:34 -0800695 Slog.d(TAG, msg);
Yao, Yuxing0811d5a2018-11-29 16:18:53 -0800696 }
697 }
Ram Periathiruvadi25c16f12017-11-17 16:48:37 -0800698}