1 /* 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 17 package android.car.drivingstate; 18 19 import android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.annotation.RequiresPermission; 22 import android.car.Car; 23 import android.car.CarManagerBase; 24 import android.os.Handler; 25 import android.os.IBinder; 26 import android.os.Looper; 27 import android.os.Message; 28 import android.os.RemoteException; 29 import android.util.Log; 30 import android.view.Display; 31 32 import com.android.internal.annotations.GuardedBy; 33 34 import java.lang.ref.WeakReference; 35 import java.util.Arrays; 36 import java.util.List; 37 import java.util.Objects; 38 39 /** 40 * API to register and get the User Experience restrictions imposed based on the car's driving 41 * state. 42 */ 43 public final class CarUxRestrictionsManager extends CarManagerBase { 44 private static final String TAG = "CarUxRManager"; 45 private static final boolean DBG = false; 46 private static final boolean VDBG = false; 47 private static final int MSG_HANDLE_UX_RESTRICTIONS_CHANGE = 0; 48 49 /** 50 * Baseline restriction mode is the default UX restrictions used for driving state. 51 * 52 * @hide 53 */ 54 public static final String UX_RESTRICTION_MODE_BASELINE = "baseline"; 55 56 private int mDisplayId = Display.INVALID_DISPLAY; 57 private final ICarUxRestrictionsManager mUxRService; 58 private final EventCallbackHandler mEventCallbackHandler; 59 @GuardedBy("this") 60 private OnUxRestrictionsChangedListener mUxRListener; 61 private CarUxRestrictionsChangeListenerToService mListenerToService; 62 63 /** @hide */ CarUxRestrictionsManager(Car car, IBinder service)64 public CarUxRestrictionsManager(Car car, IBinder service) { 65 super(car); 66 mUxRService = ICarUxRestrictionsManager.Stub.asInterface(service); 67 mEventCallbackHandler = new EventCallbackHandler(this, 68 getEventHandler().getLooper()); 69 } 70 71 /** @hide */ 72 @Override onCarDisconnected()73 public void onCarDisconnected() { 74 mListenerToService = null; 75 synchronized (this) { 76 mUxRListener = null; 77 } 78 } 79 80 /** 81 * Listener Interface for clients to implement to get updated on driving state related 82 * changes. 83 */ 84 public interface OnUxRestrictionsChangedListener { 85 /** 86 * Called when the UX restrictions due to a car's driving state changes. 87 * 88 * @param restrictionInfo The new UX restriction information 89 */ onUxRestrictionsChanged(CarUxRestrictions restrictionInfo)90 void onUxRestrictionsChanged(CarUxRestrictions restrictionInfo); 91 } 92 93 /** 94 * Registers a {@link OnUxRestrictionsChangedListener} for listening to changes in the 95 * UX Restrictions to adhere to. 96 * <p> 97 * If a listener has already been registered, it has to be unregistered before registering 98 * the new one. 99 * 100 * @param listener {@link OnUxRestrictionsChangedListener} 101 */ registerListener(@onNull OnUxRestrictionsChangedListener listener)102 public void registerListener(@NonNull OnUxRestrictionsChangedListener listener) { 103 registerListener(listener, getDisplayId()); 104 } 105 106 /** 107 * @hide 108 */ registerListener(@onNull OnUxRestrictionsChangedListener listener, int displayId)109 public void registerListener(@NonNull OnUxRestrictionsChangedListener listener, int displayId) { 110 synchronized (this) { 111 // Check if the listener has been already registered. 112 if (mUxRListener != null) { 113 if (DBG) { 114 Log.d(TAG, "Listener already registered listener"); 115 } 116 return; 117 } 118 mUxRListener = listener; 119 } 120 121 try { 122 if (mListenerToService == null) { 123 mListenerToService = new CarUxRestrictionsChangeListenerToService(this); 124 } 125 // register to the Service to listen for changes. 126 mUxRService.registerUxRestrictionsChangeListener(mListenerToService, displayId); 127 } catch (RemoteException e) { 128 handleRemoteExceptionFromCarService(e); 129 } 130 } 131 132 /** 133 * Unregisters the registered {@link OnUxRestrictionsChangedListener} 134 */ unregisterListener()135 public void unregisterListener() { 136 synchronized (this) { 137 if (mUxRListener == null) { 138 if (DBG) { 139 Log.d(TAG, "Listener was not previously registered"); 140 } 141 return; 142 } 143 mUxRListener = null; 144 } 145 try { 146 mUxRService.unregisterUxRestrictionsChangeListener(mListenerToService); 147 } catch (RemoteException e) { 148 handleRemoteExceptionFromCarService(e); 149 } 150 } 151 152 /** 153 * Sets new {@link CarUxRestrictionsConfiguration}s for next trip. 154 * <p> 155 * Saving new configurations does not affect current configuration. The new configuration will 156 * only be used after UX Restrictions service restarts when the vehicle is parked. 157 * <p> 158 * Input configurations must be one-to-one mapped to displays, namely each display must have 159 * exactly one configuration. 160 * See {@link CarUxRestrictionsConfiguration.Builder#setDisplayAddress(DisplayAddress)}. 161 * 162 * @param configs Map of display Id to UX restrictions configurations to be persisted. 163 * @return {@code true} if input config was successfully saved; {@code false} otherwise. 164 * @hide 165 */ 166 @RequiresPermission(value = Car.PERMISSION_CAR_UX_RESTRICTIONS_CONFIGURATION) saveUxRestrictionsConfigurationForNextBoot( List<CarUxRestrictionsConfiguration> configs)167 public boolean saveUxRestrictionsConfigurationForNextBoot( 168 List<CarUxRestrictionsConfiguration> configs) { 169 try { 170 return mUxRService.saveUxRestrictionsConfigurationForNextBoot(configs); 171 } catch (RemoteException e) { 172 return handleRemoteExceptionFromCarService(e, false); 173 } 174 } 175 176 /** 177 * Gets the current UX restrictions ({@link CarUxRestrictions}) in place. 178 * 179 * @return current UX restrictions that is in effect. 180 */ 181 @Nullable getCurrentCarUxRestrictions()182 public CarUxRestrictions getCurrentCarUxRestrictions() { 183 return getCurrentCarUxRestrictions(getDisplayId()); 184 } 185 186 /** 187 * @hide 188 */ 189 @Nullable getCurrentCarUxRestrictions(int displayId)190 public CarUxRestrictions getCurrentCarUxRestrictions(int displayId) { 191 try { 192 return mUxRService.getCurrentUxRestrictions(displayId); 193 } catch (RemoteException e) { 194 return handleRemoteExceptionFromCarService(e, null); 195 } 196 } 197 198 /** 199 * Sets restriction mode. Returns {@code true} if the operation succeeds. 200 * 201 * <p>The default mode is {@link #UX_RESTRICTION_MODE_BASELINE}. 202 * 203 * <p>If a new {@link CarUxRestrictions} is available upon mode transition, it'll 204 * be immediately dispatched to listeners. 205 * 206 * <p>If the given mode is not configured for current driving state, it 207 * will fall back to the default value. 208 * 209 * <p>If a configuration was set for a passenger mode before an upgrade to Android R, that 210 * passenger configuration is now called "passenger". 211 * 212 * @hide 213 */ 214 @RequiresPermission(value = Car.PERMISSION_CAR_UX_RESTRICTIONS_CONFIGURATION) setRestrictionMode(@onNull String mode)215 public boolean setRestrictionMode(@NonNull String mode) { 216 Objects.requireNonNull(mode, "mode must not be null"); 217 try { 218 return mUxRService.setRestrictionMode(mode); 219 } catch (RemoteException e) { 220 return handleRemoteExceptionFromCarService(e, false); 221 } 222 } 223 224 /** 225 * Returns the current restriction mode. 226 * 227 * <p>The default mode is {@link #UX_RESTRICTION_MODE_BASELINE}. 228 * 229 * <p>If a configuration was set for a passenger mode before an upgrade to Android R, that 230 * passenger configuration is now called "passenger". 231 * 232 * @hide 233 */ 234 @RequiresPermission(value = Car.PERMISSION_CAR_UX_RESTRICTIONS_CONFIGURATION) 235 @NonNull getRestrictionMode()236 public String getRestrictionMode() { 237 try { 238 return mUxRService.getRestrictionMode(); 239 } catch (RemoteException e) { 240 return handleRemoteExceptionFromCarService(e, null); 241 } 242 } 243 244 /** 245 * Sets a new {@link CarUxRestrictionsConfiguration} for next trip. 246 * <p> 247 * Saving a new configuration does not affect current configuration. The new configuration will 248 * only be used after UX Restrictions service restarts when the vehicle is parked. 249 * 250 * @param config UX restrictions configuration to be persisted. 251 * @return {@code true} if input config was successfully saved; {@code false} otherwise. 252 * @hide 253 */ 254 @RequiresPermission(value = Car.PERMISSION_CAR_UX_RESTRICTIONS_CONFIGURATION) saveUxRestrictionsConfigurationForNextBoot( CarUxRestrictionsConfiguration config)255 public boolean saveUxRestrictionsConfigurationForNextBoot( 256 CarUxRestrictionsConfiguration config) { 257 return saveUxRestrictionsConfigurationForNextBoot(Arrays.asList(config)); 258 } 259 260 /** 261 * Gets the staged configurations. 262 * <p> 263 * Configurations set by {@link #saveUxRestrictionsConfigurationForNextBoot(List)} do not 264 * immediately affect current drive. Instead, they are staged to take effect when car service 265 * boots up the next time. 266 * <p> 267 * This methods is only for test purpose, please do not use in production. 268 * 269 * @return current staged configuration, {@code null} if it's not available 270 * @hide 271 */ 272 @Nullable 273 @RequiresPermission(value = Car.PERMISSION_CAR_UX_RESTRICTIONS_CONFIGURATION) getStagedConfigs()274 public List<CarUxRestrictionsConfiguration> getStagedConfigs() { 275 try { 276 return mUxRService.getStagedConfigs(); 277 } catch (RemoteException e) { 278 return handleRemoteExceptionFromCarService(e, null); 279 } 280 } 281 282 /** 283 * Gets the current configurations. 284 * 285 * @return current configurations that is in effect. 286 * @hide 287 */ 288 @RequiresPermission(value = Car.PERMISSION_CAR_UX_RESTRICTIONS_CONFIGURATION) getConfigs()289 public List<CarUxRestrictionsConfiguration> getConfigs() { 290 try { 291 return mUxRService.getConfigs(); 292 } catch (RemoteException e) { 293 return handleRemoteExceptionFromCarService(e, null); 294 } 295 } 296 297 /** 298 * Class that implements the listener interface and gets called back from the 299 * {@link com.android.car.CarDrivingStateService} across the binder interface. 300 */ 301 private static class CarUxRestrictionsChangeListenerToService extends 302 ICarUxRestrictionsChangeListener.Stub { 303 private final WeakReference<CarUxRestrictionsManager> mUxRestrictionsManager; 304 CarUxRestrictionsChangeListenerToService(CarUxRestrictionsManager manager)305 public CarUxRestrictionsChangeListenerToService(CarUxRestrictionsManager manager) { 306 mUxRestrictionsManager = new WeakReference<>(manager); 307 } 308 309 @Override onUxRestrictionsChanged(CarUxRestrictions restrictionInfo)310 public void onUxRestrictionsChanged(CarUxRestrictions restrictionInfo) { 311 CarUxRestrictionsManager manager = mUxRestrictionsManager.get(); 312 if (manager != null) { 313 manager.handleUxRestrictionsChanged(restrictionInfo); 314 } 315 } 316 } 317 318 /** 319 * Gets the {@link CarUxRestrictions} from the service listener 320 * {@link CarUxRestrictionsChangeListenerToService} and dispatches it to a handler provided 321 * to the manager. 322 * 323 * @param restrictionInfo {@link CarUxRestrictions} that has been registered to listen on 324 */ handleUxRestrictionsChanged(CarUxRestrictions restrictionInfo)325 private void handleUxRestrictionsChanged(CarUxRestrictions restrictionInfo) { 326 // send a message to the handler 327 mEventCallbackHandler.sendMessage(mEventCallbackHandler.obtainMessage( 328 MSG_HANDLE_UX_RESTRICTIONS_CHANGE, restrictionInfo)); 329 } 330 331 /** 332 * Callback Handler to handle dispatching the UX restriction changes to the corresponding 333 * listeners. 334 */ 335 private static final class EventCallbackHandler extends Handler { 336 private final WeakReference<CarUxRestrictionsManager> mUxRestrictionsManager; 337 EventCallbackHandler(CarUxRestrictionsManager manager, Looper looper)338 public EventCallbackHandler(CarUxRestrictionsManager manager, Looper looper) { 339 super(looper); 340 mUxRestrictionsManager = new WeakReference<>(manager); 341 } 342 343 @Override handleMessage(Message msg)344 public void handleMessage(Message msg) { 345 CarUxRestrictionsManager mgr = mUxRestrictionsManager.get(); 346 if (mgr != null) { 347 mgr.dispatchUxRChangeToClient((CarUxRestrictions) msg.obj); 348 } 349 } 350 } 351 352 /** 353 * Checks for the listeners to list of {@link CarUxRestrictions} and calls them back 354 * in the callback handler thread. 355 * 356 * @param restrictionInfo {@link CarUxRestrictions} 357 */ dispatchUxRChangeToClient(CarUxRestrictions restrictionInfo)358 private void dispatchUxRChangeToClient(CarUxRestrictions restrictionInfo) { 359 if (restrictionInfo == null) { 360 return; 361 } 362 synchronized (this) { 363 if (mUxRListener != null) { 364 mUxRListener.onUxRestrictionsChanged(restrictionInfo); 365 } 366 } 367 } 368 getDisplayId()369 private int getDisplayId() { 370 if (mDisplayId != Display.INVALID_DISPLAY) { 371 return mDisplayId; 372 } 373 374 mDisplayId = getContext().getDisplayId(); 375 Log.i(TAG, "Context returns display ID " + mDisplayId); 376 377 if (mDisplayId == Display.INVALID_DISPLAY) { 378 mDisplayId = Display.DEFAULT_DISPLAY; 379 Log.e(TAG, "Could not retrieve display id. Using default: " + mDisplayId); 380 } 381 382 return mDisplayId; 383 } 384 } 385