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