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