1 /*
2  * Copyright (C) 2022 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;
18 
19 
20 import android.annotation.CallbackExecutor;
21 import android.annotation.IntDef;
22 import android.annotation.NonNull;
23 import android.annotation.Nullable;
24 import android.annotation.RequiresPermission;
25 import android.annotation.SystemApi;
26 import android.car.CarOccupantZoneManager.OccupantZoneInfo;
27 import android.car.builtin.util.Slogf;
28 import android.car.occupantconnection.ICarRemoteDevice;
29 import android.car.occupantconnection.IStateCallback;
30 import android.content.pm.PackageInfo;
31 import android.os.Binder;
32 import android.os.IBinder;
33 import android.os.RemoteException;
34 
35 import com.android.internal.annotations.GuardedBy;
36 import com.android.internal.util.Preconditions;
37 
38 import java.lang.annotation.Retention;
39 import java.lang.annotation.RetentionPolicy;
40 import java.util.Objects;
41 import java.util.concurrent.Executor;
42 
43 /**
44  * API for monitoring the states of occupant zones in the car, managing their power, and monitoring
45  * peer clients in those occupant zones.
46  * <p>
47  * Unless specified explicitly, a client means an app that uses this API and runs as a
48  * foreground user in an occupant zone, while a peer client means an app that has the same package
49  * name as the caller app and runs as another foreground user (in another occupant zone or even
50  * another Android system).
51  *
52  * @hide
53  */
54 @SystemApi
55 public final class CarRemoteDeviceManager extends CarManagerBase {
56 
57     private static final String TAG = CarRemoteDeviceManager.class.getSimpleName();
58 
59     /**
60      * Flag to indicate whether all the displays of the occupant zone are powered on. If any of
61      * them is not powered on, the caller can power them on by {@link #setOccupantZonePower}.
62      */
63     public static final int FLAG_OCCUPANT_ZONE_POWER_ON = 1 << 0;
64 
65     /**
66      * Flag to indicate whether the main display of the occupant zone is unlocked. When it is
67      * locked, it can't display UI. If UI is needed to establish the connection (for example, it
68      * needs to show a dialog to get user approval), the caller shouldn't request a connection to
69      * the occupant zone when it's locked.
70      */
71     public static final int FLAG_OCCUPANT_ZONE_SCREEN_UNLOCKED = 1 << 1;
72 
73     /**
74      * Flag to indicate whether the occupant zone is ready for connection.
75      * If it is ready and the peer app is installed in it, the caller can call {@link
76      * android.car.occupantconnection.CarOccupantConnectionManager#requestConnection} to connect to
77      * the client app in the occupant zone. Note: if UI is needed, the caller should make sure the
78      * main display of the occupant zone is unlocked and the client app is running in the foreground
79      * before requesting a connection to it.
80      */
81     public static final int FLAG_OCCUPANT_ZONE_CONNECTION_READY = 1 << 2;
82 
83     /**
84      * Flag to indicate whether the client app is installed in the occupant zone. If it's not
85      * installed, the caller may show a Dialog to promote the user to install the app.
86      */
87     public static final int FLAG_CLIENT_INSTALLED = 1 << 0;
88 
89     /**
90      * Flag to indicate whether the client app with the same long version code ({@link
91      * PackageInfo#getLongVersionCode} is installed in the occupant zone. If it's not installed,
92      * the caller may show a Dialog to promote the user to install or update the app. To get
93      * detailed package info of the client app, the caller can call {@link #getEndpointPackageInfo}.
94      */
95     public static final int FLAG_CLIENT_SAME_LONG_VERSION = 1 << 1;
96 
97     /**
98      * Flag to indicate whether the client app with the same signing info ({@link
99      * PackageInfo#signingInfo} is installed in the occupant zone. If it's not installed, the caller
100      * may show a Dialog to promote the user to install or update the app. To get detailed
101      * package info of the client app, the caller can call {@link #getEndpointPackageInfo}.
102      */
103     public static final int FLAG_CLIENT_SAME_SIGNATURE = 1 << 2;
104 
105     /**
106      * Flag to indicate whether the client app in the occupant zone is running. If it's not running,
107      * the caller may show a Dialog to promote the user to start the app.
108      */
109     public static final int FLAG_CLIENT_RUNNING = 1 << 3;
110 
111     /**
112      * Flag to indicate whether the client app in the occupant zone is running in the foreground
113      * (vs background). If UI is needed, the caller shouldn't request a connection to the client app
114      * when the client app is running in the background.
115      */
116     public static final int FLAG_CLIENT_IN_FOREGROUND = 1 << 4;
117 
118     /**
119      * Flags for the state of the occupant zone.
120      *
121      * @hide
122      */
123     @IntDef(flag = true, prefix = {"FLAG_OCCUPANT_ZONE_"}, value = {
124             FLAG_OCCUPANT_ZONE_POWER_ON,
125             FLAG_OCCUPANT_ZONE_SCREEN_UNLOCKED,
126             FLAG_OCCUPANT_ZONE_CONNECTION_READY,
127     })
128     @Retention(RetentionPolicy.SOURCE)
129     public @interface OccupantZoneState {
130     }
131 
132     /**
133      * Flags for the state of client app in the occupant zone.
134      *
135      * @hide
136      */
137     @IntDef(flag = true, prefix = {"FLAG_CLIENT_"}, value = {
138             FLAG_CLIENT_INSTALLED,
139             FLAG_CLIENT_SAME_LONG_VERSION,
140             FLAG_CLIENT_SAME_SIGNATURE,
141             FLAG_CLIENT_RUNNING,
142             FLAG_CLIENT_IN_FOREGROUND
143     })
144     @Retention(RetentionPolicy.SOURCE)
145     public @interface AppState {
146     }
147 
148     /**
149      * A callback to allow the client to monitor other occupant zones in the car and peer clients
150      * in those occupant zones.
151      * <p>
152      * The caller can call {@link
153      * android.car.occupantconnection.CarOccupantConnectionManager#requestConnection} to connect to
154      * its peer client once the state of the peer occupant zone is {@link
155      * #FLAG_OCCUPANT_ZONE_CONNECTION_READY}  and the state of the peer client becomes {@link
156      * android.car.CarRemoteDeviceManager#FLAG_CLIENT_INSTALLED}. If UI is needed to establish
157      * the connection, the caller must wait until {@link
158      * android.car.CarRemoteDeviceManager#FLAG_OCCUPANT_ZONE_SCREEN_UNLOCKED} and {@link
159      * android.car.CarRemoteDeviceManager#FLAG_CLIENT_IN_FOREGROUND}) before requesting a
160      * connection.
161      */
162     public interface StateCallback {
163         /**
164          * Invoked when the callback is registered, or when the {@link OccupantZoneState} of the
165          * occupant zone has changed.
166          *
167          * @param occupantZoneStates the state of the occupant zone. Multiple flags can be set in
168          *                           the state.
169          */
onOccupantZoneStateChanged(@onNull OccupantZoneInfo occupantZone, @OccupantZoneState int occupantZoneStates)170         void onOccupantZoneStateChanged(@NonNull OccupantZoneInfo occupantZone,
171                 @OccupantZoneState int occupantZoneStates);
172 
173         /**
174          * Invoked when the callback is registered, or when the {@link AppState} of the peer app in
175          * the given occupant zone has changed.
176          * <p>
177          * Note: Apps sharing the same user ID through the "sharedUserId" mechanism won't get
178          * notified when the running state of their peer apps has changed.
179          *
180          * @param appStates the state of the peer app. Multiple flags can be set in the state.
181          */
onAppStateChanged(@onNull OccupantZoneInfo occupantZone, @AppState int appStates)182         void onAppStateChanged(@NonNull OccupantZoneInfo occupantZone,
183                 @AppState int appStates);
184     }
185 
186     private final Object mLock = new Object();
187     private final ICarRemoteDevice mService;
188     private final String mPackageName;
189 
190     @GuardedBy("mLock")
191     private StateCallback mCallback;
192     @GuardedBy("mLock")
193     private Executor mCallbackExecutor;
194 
195     private final IStateCallback mBinderCallback = new IStateCallback.Stub() {
196         @Override
197         public void onOccupantZoneStateChanged(OccupantZoneInfo occupantZone,
198                 int occupantZoneStates) {
199             StateCallback callback;
200             Executor callbackExecutor;
201             synchronized (CarRemoteDeviceManager.this.mLock) {
202                 callback = mCallback;
203                 callbackExecutor = mCallbackExecutor;
204             }
205             if (callback == null || callbackExecutor == null) {
206                 // This should never happen, but let's be cautious.
207                 Slogf.e(TAG, "Failed to notify occupant zone state change because "
208                         + "the callback was unregistered! callback: " + callback
209                         + ", callbackExecutor: " + callbackExecutor);
210                 return;
211             }
212             long token = Binder.clearCallingIdentity();
213             try {
214                 callbackExecutor.execute(() -> callback.onOccupantZoneStateChanged(
215                         occupantZone, occupantZoneStates));
216             } finally {
217                 Binder.restoreCallingIdentity(token);
218             }
219         }
220 
221         @Override
222         public void onAppStateChanged(OccupantZoneInfo occupantZone, int appStates) {
223             StateCallback callback;
224             Executor callbackExecutor;
225             synchronized (CarRemoteDeviceManager.this.mLock) {
226                 callback = mCallback;
227                 callbackExecutor = mCallbackExecutor;
228             }
229             if (callback == null || callbackExecutor == null) {
230                 // This should never happen, but let's be cautious.
231                 Slogf.e(TAG, "Failed to notify app state change because the "
232                         + "callback was unregistered! callback: " + callback
233                         + ", callbackExecutor: " + callbackExecutor);
234                 return;
235             }
236             long token = Binder.clearCallingIdentity();
237             try {
238                 callbackExecutor.execute(() ->
239                         callback.onAppStateChanged(occupantZone, appStates));
240             } finally {
241                 Binder.restoreCallingIdentity(token);
242             }
243         }
244     };
245 
246     /** @hide */
CarRemoteDeviceManager(Car car, IBinder service)247     public CarRemoteDeviceManager(Car car, IBinder service) {
248         super(car);
249         mService = ICarRemoteDevice.Stub.asInterface(service);
250         mPackageName = mCar.getContext().getPackageName();
251     }
252 
253     /** @hide */
254     @Override
onCarDisconnected()255     public void onCarDisconnected() {
256         synchronized (mLock) {
257             mCallback = null;
258             mCallbackExecutor = null;
259         }
260     }
261 
262     /**
263      * Registers the {@code callback} to monitor the states of other occupant zones in the car and
264      * the peer clients in those occupant zones.
265      * <p>
266      * The client app can only register one {@link StateCallback}.
267      * The client app should call this method before requesting connection to its peer clients in
268      * other occupant zones.
269      *
270      * @param executor the Executor to run the callback
271      * @throws IllegalStateException if this client already registered a {@link StateCallback}
272      */
273     @RequiresPermission(Car.PERMISSION_MANAGE_REMOTE_DEVICE)
registerStateCallback(@onNull @allbackExecutor Executor executor, @NonNull StateCallback callback)274     public void registerStateCallback(@NonNull @CallbackExecutor Executor executor,
275             @NonNull StateCallback callback) {
276         Objects.requireNonNull(executor, "executor cannot be null");
277         Objects.requireNonNull(callback, "callback cannot be null");
278         synchronized (mLock) {
279             Preconditions.checkState(mCallback == null,
280                     "A StateCallback was registered already");
281             try {
282                 mService.registerStateCallback(mPackageName, mBinderCallback);
283                 mCallback = callback;
284                 mCallbackExecutor = executor;
285             } catch (RemoteException e) {
286                 Slogf.e(TAG, "Failed to register StateCallback");
287                 handleRemoteExceptionFromCarService(e);
288             }
289         }
290     }
291 
292     /**
293      * Unregisters the existing {@link StateCallback}.
294      * <p>
295      * This method can be called after calling {@link #registerStateCallback}, as soon
296      * as this caller no longer needs to monitor other occupant zones or becomes inactive.
297      * After monitoring ends, established connections won't be affected. In other words, {@link
298      * android.car.occupantconnection.Payload} can still be sent.
299      *
300      * @throws IllegalStateException if no {@link StateCallback} was registered by
301      *                               this {@link CarRemoteDeviceManager} before
302      */
303     @RequiresPermission(Car.PERMISSION_MANAGE_REMOTE_DEVICE)
unregisterStateCallback()304     public void unregisterStateCallback() {
305         synchronized (mLock) {
306             Preconditions.checkState(mCallback != null, "There is no StateCallback "
307                     + "registered by this CarRemoteDeviceManager");
308             mCallback = null;
309             mCallbackExecutor = null;
310             try {
311                 mService.unregisterStateCallback(mPackageName);
312             } catch (RemoteException e) {
313                 Slogf.e(TAG, "Failed to unregister StateCallback");
314                 handleRemoteExceptionFromCarService(e);
315             }
316         }
317     }
318 
319     /**
320      * Returns the {@link PackageInfo} of the client in {@code occupantZone}, or
321      * {@code null} if there is no such client or an error occurred.
322      */
323     @RequiresPermission(Car.PERMISSION_MANAGE_REMOTE_DEVICE)
324     @Nullable
getEndpointPackageInfo(@onNull OccupantZoneInfo occupantZone)325     public PackageInfo getEndpointPackageInfo(@NonNull OccupantZoneInfo occupantZone) {
326         Objects.requireNonNull(occupantZone, "occupantZone cannot be null");
327         try {
328             return mService.getEndpointPackageInfo(occupantZone.zoneId, mPackageName);
329         } catch (RemoteException e) {
330             Slogf.e(TAG, "Failed to get peer endpoint PackageInfo in " + occupantZone);
331             return handleRemoteExceptionFromCarService(e, null);
332         }
333     }
334 
335     /**
336      * If {@code powerOn} is {@code true}, powers on all the displays of the given {@code
337      * occupantZone}, and powers on the associated Android system. If {@code powerOn} is
338      * {@code false}, powers off all the displays of given {@code occupantZone}, but doesn't
339      * power off the associated Android system.
340      * <p>
341      * It is not allowed to control the power of the driver occupant zone.
342      *
343      * @throws UnsupportedOperationException if {@code occupantZone} represents the driver occupant
344      *                                       zone
345      */
346     @RequiresPermission(allOf = {Car.PERMISSION_CAR_POWER, Car.PERMISSION_MANAGE_REMOTE_DEVICE})
setOccupantZonePower(@onNull OccupantZoneInfo occupantZone, boolean powerOn)347     public void setOccupantZonePower(@NonNull OccupantZoneInfo occupantZone, boolean powerOn) {
348         Objects.requireNonNull(occupantZone, "occupantZone cannot be null");
349         try {
350             mService.setOccupantZonePower(occupantZone, powerOn);
351         } catch (RemoteException e) {
352             Slogf.e(TAG, "Failed to control the power of " + occupantZone);
353             handleRemoteExceptionFromCarService(e);
354         }
355     }
356 
357     /**
358      * Returns {@code true} if the associated Android system AND all the displays of the given
359      * {@code occupantZone} are powered on. Returns {@code false} otherwise.
360      */
361     @RequiresPermission(Car.PERMISSION_MANAGE_REMOTE_DEVICE)
isOccupantZonePowerOn(@onNull OccupantZoneInfo occupantZone)362     public boolean isOccupantZonePowerOn(@NonNull OccupantZoneInfo occupantZone) {
363         Objects.requireNonNull(occupantZone, "occupantZone cannot be null");
364         try {
365             return mService.isOccupantZonePowerOn(occupantZone);
366         } catch (RemoteException e) {
367             Slogf.e(TAG, "Failed to get power state of " + occupantZone);
368             return handleRemoteExceptionFromCarService(e, false);
369         }
370     }
371 }
372