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