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