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