1 /* 2 * Copyright (C) 2021 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.cluster; 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.Intent; 26 import android.os.Bundle; 27 import android.os.IBinder; 28 import android.os.RemoteException; 29 30 import com.android.internal.annotations.VisibleForTesting; 31 32 import java.lang.annotation.Retention; 33 import java.lang.annotation.RetentionPolicy; 34 import java.lang.ref.WeakReference; 35 import java.util.Objects; 36 import java.util.concurrent.CopyOnWriteArrayList; 37 import java.util.concurrent.Executor; 38 39 /** @hide */ 40 public class ClusterHomeManager extends CarManagerBase { 41 private static final String TAG = ClusterHomeManager.class.getSimpleName(); 42 /** 43 * When the client reports ClusterHome state and if there is no UI in the sub area, it can 44 * reports UI_TYPE_CLUSTER_NONE instead. 45 */ 46 public static final int UI_TYPE_CLUSTER_NONE = -1; 47 public static final int UI_TYPE_CLUSTER_HOME = 0; 48 49 /** @hide */ 50 @IntDef(flag = true, prefix = { "CONFIG_" }, value = { 51 CONFIG_DISPLAY_ON_OFF, 52 CONFIG_DISPLAY_BOUNDS, 53 CONFIG_DISPLAY_INSETS, 54 CONFIG_UI_TYPE, 55 }) 56 @Retention(RetentionPolicy.SOURCE) 57 public @interface Config {} 58 59 /** Bit fields indicates which fields of {@link ClusterState} are changed */ 60 public static final int CONFIG_DISPLAY_ON_OFF = 0x01; 61 public static final int CONFIG_DISPLAY_BOUNDS = 0x02; 62 public static final int CONFIG_DISPLAY_INSETS = 0x04; 63 public static final int CONFIG_UI_TYPE = 0x08; 64 public static final int CONFIG_DISPLAY_ID = 0x10; 65 66 /** 67 * Callback for ClusterHome to get notifications when cluster state changes. 68 */ 69 public interface ClusterStateListener { 70 /** 71 * Called when ClusterOS changes the cluster display state, the geometry of cluster display, 72 * or the uiType. 73 * @param state newly updated {@link ClusterState} 74 * @param changes the flag indicates which fields are updated 75 */ onClusterStateChanged(ClusterState state, @Config int changes)76 void onClusterStateChanged(ClusterState state, @Config int changes); 77 } 78 79 /** 80 * Callback for ClusterHome to get notifications when cluster navigation state changes. 81 */ 82 public interface ClusterNavigationStateListener { 83 /** Called when the App who owns the navigation focus casts the new navigation state. */ onNavigationState(byte[] navigationState)84 void onNavigationState(byte[] navigationState); 85 } 86 87 private static class ClusterStateListenerRecord { 88 final Executor mExecutor; 89 final ClusterStateListener mListener; ClusterStateListenerRecord(Executor executor, ClusterStateListener listener)90 ClusterStateListenerRecord(Executor executor, ClusterStateListener listener) { 91 mExecutor = executor; 92 mListener = listener; 93 } 94 @Override equals(Object obj)95 public boolean equals(Object obj) { 96 if (this == obj) { 97 return true; 98 } 99 if (!(obj instanceof ClusterStateListenerRecord)) { 100 return false; 101 } 102 return mListener == ((ClusterStateListenerRecord) obj).mListener; 103 } 104 @Override hashCode()105 public int hashCode() { 106 return mListener.hashCode(); 107 } 108 } 109 110 private static class ClusterNavigationStateListenerRecord { 111 final Executor mExecutor; 112 final ClusterNavigationStateListener mListener; 113 ClusterNavigationStateListenerRecord(Executor executor, ClusterNavigationStateListener listener)114 ClusterNavigationStateListenerRecord(Executor executor, 115 ClusterNavigationStateListener listener) { 116 mExecutor = executor; 117 mListener = listener; 118 } 119 @Override equals(Object obj)120 public boolean equals(Object obj) { 121 if (this == obj) { 122 return true; 123 } 124 if (!(obj instanceof ClusterNavigationStateListenerRecord)) { 125 return false; 126 } 127 return mListener == ((ClusterNavigationStateListenerRecord) obj).mListener; 128 } 129 @Override hashCode()130 public int hashCode() { 131 return mListener.hashCode(); 132 } 133 } 134 135 private final IClusterHomeService mService; 136 private final IClusterStateListenerImpl mClusterStateListenerBinderCallback; 137 private final IClusterNavigationStateListenerImpl mClusterNavigationStateListenerBinderCallback; 138 private final CopyOnWriteArrayList<ClusterStateListenerRecord> mStateListeners = 139 new CopyOnWriteArrayList<>(); 140 private final CopyOnWriteArrayList<ClusterNavigationStateListenerRecord> 141 mNavigationStateListeners = new CopyOnWriteArrayList<>(); 142 143 /** @hide */ 144 @VisibleForTesting ClusterHomeManager(Car car, IBinder service)145 public ClusterHomeManager(Car car, IBinder service) { 146 super(car); 147 mService = IClusterHomeService.Stub.asInterface(service); 148 mClusterStateListenerBinderCallback = new IClusterStateListenerImpl(this); 149 mClusterNavigationStateListenerBinderCallback = 150 new IClusterNavigationStateListenerImpl(this); 151 } 152 153 /** 154 * Registers the callback for ClusterHome. 155 */ 156 @RequiresPermission(Car.PERMISSION_CAR_INSTRUMENT_CLUSTER_CONTROL) registerClusterStateListener( @onNull Executor executor, @NonNull ClusterStateListener callback)157 public void registerClusterStateListener( 158 @NonNull Executor executor, @NonNull ClusterStateListener callback) { 159 Objects.requireNonNull(executor, "executor cannot be null"); 160 Objects.requireNonNull(callback, "callback cannot be null"); 161 ClusterStateListenerRecord clusterStateListenerRecord = 162 new ClusterStateListenerRecord(executor, callback); 163 if (!mStateListeners.addIfAbsent(clusterStateListenerRecord)) { 164 return; 165 } 166 if (mStateListeners.size() == 1) { 167 try { 168 mService.registerClusterStateListener(mClusterStateListenerBinderCallback); 169 } catch (RemoteException e) { 170 handleRemoteExceptionFromCarService(e); 171 } 172 } 173 } 174 175 /** 176 * Registers the callback for ClusterHome. 177 */ 178 @RequiresPermission(Car.PERMISSION_CAR_MONITOR_CLUSTER_NAVIGATION_STATE) registerClusterNavigationStateListener( @onNull Executor executor, @NonNull ClusterNavigationStateListener callback)179 public void registerClusterNavigationStateListener( 180 @NonNull Executor executor, @NonNull ClusterNavigationStateListener callback) { 181 Objects.requireNonNull(executor, "executor cannot be null"); 182 Objects.requireNonNull(callback, "callback cannot be null"); 183 ClusterNavigationStateListenerRecord clusterStateListenerRecord = 184 new ClusterNavigationStateListenerRecord(executor, callback); 185 if (!mNavigationStateListeners.addIfAbsent(clusterStateListenerRecord)) { 186 return; 187 } 188 if (mNavigationStateListeners.size() == 1) { 189 try { 190 mService.registerClusterNavigationStateListener( 191 mClusterNavigationStateListenerBinderCallback); 192 } catch (RemoteException e) { 193 handleRemoteExceptionFromCarService(e); 194 } 195 } 196 } 197 198 /** 199 * Unregisters the callback. 200 */ 201 @RequiresPermission(Car.PERMISSION_CAR_INSTRUMENT_CLUSTER_CONTROL) unregisterClusterStateListener(@onNull ClusterStateListener callback)202 public void unregisterClusterStateListener(@NonNull ClusterStateListener callback) { 203 Objects.requireNonNull(callback, "callback cannot be null"); 204 if (!mStateListeners 205 .remove(new ClusterStateListenerRecord(/* executor= */ null, callback))) { 206 return; 207 } 208 if (mStateListeners.isEmpty()) { 209 try { 210 mService.unregisterClusterStateListener(mClusterStateListenerBinderCallback); 211 } catch (RemoteException ignored) { 212 // ignore for unregistering 213 } 214 } 215 } 216 217 /** 218 * Unregisters the callback. 219 */ 220 @RequiresPermission(Car.PERMISSION_CAR_MONITOR_CLUSTER_NAVIGATION_STATE) unregisterClusterNavigationStateListener( @onNull ClusterNavigationStateListener callback)221 public void unregisterClusterNavigationStateListener( 222 @NonNull ClusterNavigationStateListener callback) { 223 Objects.requireNonNull(callback, "callback cannot be null"); 224 if (!mNavigationStateListeners.remove(new ClusterNavigationStateListenerRecord( 225 /* executor= */ null, callback))) { 226 return; 227 } 228 if (mNavigationStateListeners.isEmpty()) { 229 try { 230 mService.unregisterClusterNavigationStateListener( 231 mClusterNavigationStateListenerBinderCallback); 232 } catch (RemoteException ignored) { 233 // ignore for unregistering 234 } 235 } 236 } 237 238 private static class IClusterStateListenerImpl extends IClusterStateListener.Stub { 239 private final WeakReference<ClusterHomeManager> mManager; 240 IClusterStateListenerImpl(ClusterHomeManager manager)241 private IClusterStateListenerImpl(ClusterHomeManager manager) { 242 mManager = new WeakReference<>(manager); 243 } 244 245 @Override onClusterStateChanged(@onNull ClusterState state, @Config int changes)246 public void onClusterStateChanged(@NonNull ClusterState state, @Config int changes) { 247 ClusterHomeManager manager = mManager.get(); 248 if (manager != null) { 249 for (ClusterStateListenerRecord cb : manager.mStateListeners) { 250 cb.mExecutor.execute( 251 () -> cb.mListener.onClusterStateChanged(state, changes)); 252 } 253 } 254 } 255 } 256 257 private static class IClusterNavigationStateListenerImpl extends 258 IClusterNavigationStateListener.Stub { 259 private final WeakReference<ClusterHomeManager> mManager; 260 IClusterNavigationStateListenerImpl(ClusterHomeManager manager)261 private IClusterNavigationStateListenerImpl(ClusterHomeManager manager) { 262 mManager = new WeakReference<>(manager); 263 } 264 265 @Override onNavigationStateChanged(@onNull byte[] navigationState)266 public void onNavigationStateChanged(@NonNull byte[] navigationState) { 267 ClusterHomeManager manager = mManager.get(); 268 if (manager != null) { 269 for (ClusterNavigationStateListenerRecord lr : manager.mNavigationStateListeners) { 270 lr.mExecutor.execute(() -> lr.mListener.onNavigationState(navigationState)); 271 } 272 } 273 } 274 } 275 276 /** 277 * Reports the current ClusterUI state. 278 * @param uiTypeMain uiType that ClusterHome tries to show in main area 279 * @param uiTypeSub uiType that ClusterHome tries to show in sub area 280 * @param uiAvailability the byte array to represent the availability of ClusterUI. 281 * 0 indicates non-available and 1 indicates available. 282 * Index 0 is reserved for ClusterHome, The other indexes are followed by OEM's definition. 283 */ 284 @RequiresPermission(Car.PERMISSION_CAR_INSTRUMENT_CLUSTER_CONTROL) reportState(int uiTypeMain, int uiTypeSub, @NonNull byte[] uiAvailability)285 public void reportState(int uiTypeMain, int uiTypeSub, @NonNull byte[] uiAvailability) { 286 try { 287 mService.reportState(uiTypeMain, uiTypeSub, uiAvailability); 288 } catch (RemoteException e) { 289 handleRemoteExceptionFromCarService(e); 290 } 291 } 292 293 /** 294 * Requests to turn the cluster display on to show some ClusterUI. 295 * @param uiType uiType that ClusterHome tries to show in main area 296 */ 297 @RequiresPermission(Car.PERMISSION_CAR_INSTRUMENT_CLUSTER_CONTROL) requestDisplay(int uiType)298 public void requestDisplay(int uiType) { 299 try { 300 mService.requestDisplay(uiType); 301 } catch (RemoteException e) { 302 handleRemoteExceptionFromCarService(e); 303 } 304 } 305 306 /** 307 * Returns the current {@code ClusterState}. 308 */ 309 @RequiresPermission(Car.PERMISSION_CAR_INSTRUMENT_CLUSTER_CONTROL) 310 @Nullable getClusterState()311 public ClusterState getClusterState() { 312 ClusterState state = null; 313 try { 314 state = mService.getClusterState(); 315 } catch (RemoteException e) { 316 handleRemoteExceptionFromCarService(e); 317 } 318 return state; 319 } 320 321 /** 322 * Start an activity as specified user. The activity is considered as in fixed mode for 323 * the cluster display and will be re-launched if the activity crashes, the package 324 * is updated or goes to background for whatever reason. 325 * Only one activity can exist in fixed mode for the display and calling this multiple 326 * times with different {@code Intent} will lead into making all previous activities into 327 * non-fixed normal state (= will not be re-launched.) 328 * @param intent the Intent to start 329 * @param options additional options for how the Activity should be started 330 * @param userId the user the new activity should run as 331 * @return true if it launches the given Intent as FixedActivity successfully 332 */ 333 @RequiresPermission(Car.PERMISSION_CAR_INSTRUMENT_CLUSTER_CONTROL) startFixedActivityModeAsUser( Intent intent, @Nullable Bundle options, int userId)334 public boolean startFixedActivityModeAsUser( 335 Intent intent, @Nullable Bundle options, int userId) { 336 try { 337 return mService.startFixedActivityModeAsUser(intent, options, userId); 338 } catch (RemoteException e) { 339 handleRemoteExceptionFromCarService(e); 340 } 341 return false; 342 } 343 344 /** 345 * The activity launched on the cluster display is no longer in fixed mode. Re-launching or 346 * finishing should not trigger re-launching any more. Note that Activity for non-current user 347 * will be auto-stopped and there is no need to call this for user switching. Note that this 348 * does not stop the activity but it will not be re-launched any more. 349 */ 350 @RequiresPermission(Car.PERMISSION_CAR_INSTRUMENT_CLUSTER_CONTROL) stopFixedActivityMode()351 public void stopFixedActivityMode() { 352 try { 353 mService.stopFixedActivityMode(); 354 } catch (RemoteException e) { 355 handleRemoteExceptionFromCarService(e); 356 } 357 } 358 359 @Override onCarDisconnected()360 protected void onCarDisconnected() { 361 mStateListeners.clear(); 362 mNavigationStateListeners.clear(); 363 } 364 } 365