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