• 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 com.android.car.cluster;
18 
19 import static android.car.builtin.app.ActivityManagerHelper.createActivityOptions;
20 import static android.content.Intent.ACTION_MAIN;
21 
22 import static com.android.car.PermissionHelper.checkHasDumpPermissionGranted;
23 import static com.android.car.hal.ClusterHalService.DISPLAY_OFF;
24 import static com.android.car.hal.ClusterHalService.DISPLAY_ON;
25 import static com.android.car.hal.ClusterHalService.DONT_CARE;
26 import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO;
27 
28 import android.app.ActivityOptions;
29 import android.car.Car;
30 import android.car.CarOccupantZoneManager;
31 import android.car.ICarOccupantZoneCallback;
32 import android.car.builtin.os.UserManagerHelper;
33 import android.car.builtin.util.Slogf;
34 import android.car.cluster.ClusterHomeManager;
35 import android.car.cluster.ClusterState;
36 import android.car.cluster.IClusterHomeService;
37 import android.car.cluster.IClusterNavigationStateListener;
38 import android.car.cluster.IClusterStateListener;
39 import android.car.cluster.navigation.NavigationState.NavigationStateProto;
40 import android.car.navigation.CarNavigationInstrumentCluster;
41 import android.content.ComponentName;
42 import android.content.Context;
43 import android.content.Intent;
44 import android.content.pm.PackageManager;
45 import android.graphics.Insets;
46 import android.graphics.Point;
47 import android.graphics.Rect;
48 import android.hardware.display.DisplayManager;
49 import android.os.Bundle;
50 import android.os.RemoteCallbackList;
51 import android.os.RemoteException;
52 import android.text.TextUtils;
53 import android.util.proto.ProtoOutputStream;
54 import android.view.Display;
55 import android.view.SurfaceControl;
56 
57 import com.android.car.CarLog;
58 import com.android.car.CarOccupantZoneService;
59 import com.android.car.CarServiceBase;
60 import com.android.car.R;
61 import com.android.car.am.FixedActivityService;
62 import com.android.car.hal.ClusterHalService;
63 import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
64 import com.android.car.internal.util.IndentingPrintWriter;
65 
66 /**
67  * Service responsible for interactions between ClusterOS and ClusterHome.
68  */
69 public final class ClusterHomeService extends IClusterHomeService.Stub
70         implements CarServiceBase, ClusterNavigationService.ClusterNavigationServiceCallback,
71         ClusterHalService.ClusterHalEventCallback {
72     private static final String TAG = CarLog.TAG_CLUSTER;
73     private static final int DEFAULT_MIN_UPDATE_INTERVAL_MILLIS = 1000;
74     private static final String NAV_STATE_PROTO_BUNDLE_KEY = "navstate2";
75 
76     private final Context mContext;
77     private final ClusterHalService mClusterHalService;
78     private final ClusterNavigationService mClusterNavigationService;
79     private final CarOccupantZoneService mOccupantZoneService;
80     private final FixedActivityService mFixedActivityService;
81     private final ComponentName mClusterHomeActivity;
82     private final ClusterHealthMonitor mClusterHealthMonitor;
83 
84     private boolean mServiceEnabled;
85 
86     private int mClusterDisplayId = Display.INVALID_DISPLAY;
87 
88     private int mOnOff = DISPLAY_OFF;
89     private Rect mBounds = new Rect();
90     private Insets mInsets = Insets.NONE;
91     private int mUiType = ClusterHomeManager.UI_TYPE_CLUSTER_HOME;
92     private Intent mLastIntent;
93     private int mLastIntentUserId = UserManagerHelper.USER_SYSTEM;
94 
95     private final RemoteCallbackList<IClusterStateListener> mClientListeners =
96             new RemoteCallbackList<>();
97 
98     private final RemoteCallbackList<IClusterNavigationStateListener> mClientNavigationListeners =
99             new RemoteCallbackList<>();
100 
ClusterHomeService(Context context, ClusterHalService clusterHalService, ClusterNavigationService navigationService, CarOccupantZoneService occupantZoneService, FixedActivityService fixedActivityService)101     public ClusterHomeService(Context context, ClusterHalService clusterHalService,
102             ClusterNavigationService navigationService,
103             CarOccupantZoneService occupantZoneService,
104             FixedActivityService fixedActivityService) {
105         mContext = context;
106         mClusterHalService = clusterHalService;
107         mClusterNavigationService = navigationService;
108         mOccupantZoneService = occupantZoneService;
109         mFixedActivityService = fixedActivityService;
110         mClusterHomeActivity = ComponentName.unflattenFromString(
111                 mContext.getString(R.string.config_clusterHomeActivity));
112         mClusterHealthMonitor = new ClusterHealthMonitor(mContext, mClusterHalService);
113         mLastIntent = new Intent(ACTION_MAIN).setComponent(mClusterHomeActivity);
114     }
115 
116     @Override
init()117     public void init() {
118         Slogf.d(TAG, "initClusterHomeService");
119         if (TextUtils.isEmpty(mClusterHomeActivity.getPackageName())
120                 || TextUtils.isEmpty(mClusterHomeActivity.getClassName())) {
121             Slogf.i(TAG, "Improper ClusterHomeActivity: %s", mClusterHomeActivity);
122             return;
123         }
124         if (!mClusterHalService.isServiceEnabled()) {
125             Slogf.e(TAG, "ClusterHomeService is disabled. To enable, it must be either in LIGHT "
126                     + "mode, or all core properties must be defined in FULL mode.");
127             return;
128         }
129         // In FULL mode mOnOff is set to 'OFF', and can be changed by the CLUSTER_DISPLAY_STATE
130         // property. In LIGHT mode, we set it to 'ON' because the CLUSTER_DISPLAY_STATE property may
131         // not be available, and we do not subscribe to it.
132         if (mClusterHalService.isLightMode()) {
133             mOnOff = DISPLAY_ON;
134         }
135 
136         mServiceEnabled = true;
137         mClusterHalService.setCallback(this);
138         mClusterNavigationService.setClusterServiceCallback(this);
139 
140         mOccupantZoneService.registerCallback(mOccupantZoneCallback);
141 
142         initClusterDisplay();
143     }
144 
initClusterDisplay()145     private void initClusterDisplay() {
146         int clusterDisplayId = mOccupantZoneService.getDisplayIdForDriver(
147                 CarOccupantZoneManager.DISPLAY_TYPE_INSTRUMENT_CLUSTER);
148         Slogf.d(TAG, "initClusterDisplay: displayId=%d", clusterDisplayId);
149         if (clusterDisplayId == Display.INVALID_DISPLAY) {
150             Slogf.i(TAG, "No cluster display is defined");
151         }
152         if (clusterDisplayId == mClusterDisplayId) {
153             return;  // Skip if the cluster display isn't changed.
154         }
155         mClusterDisplayId = clusterDisplayId;
156         sendDisplayState(ClusterHomeManager.CONFIG_DISPLAY_ID);
157         if (clusterDisplayId == Display.INVALID_DISPLAY) {
158             return;
159         }
160 
161         // Initialize mBounds only once.
162         if (mBounds.right == 0 && mBounds.bottom == 0 && mBounds.left == 0 && mBounds.top == 0) {
163             DisplayManager displayManager = mContext.getSystemService(DisplayManager.class);
164             Display clusterDisplay = displayManager.getDisplay(clusterDisplayId);
165             Point size = new Point();
166             clusterDisplay.getRealSize(size);
167             mBounds.right = size.x;
168             mBounds.bottom = size.y;
169             Slogf.d(TAG, "Found cluster displayId=%d, bounds=%s", clusterDisplayId, mBounds);
170         }
171 
172         ActivityOptions activityOptions = ActivityOptions.makeBasic()
173                 .setLaunchDisplayId(clusterDisplayId);
174         mFixedActivityService.startFixedActivityModeForDisplayAndUser(
175                 mLastIntent, activityOptions, clusterDisplayId, mLastIntentUserId);
176 
177         Slogf.i(TAG, "Initialized cluster display %d", clusterDisplayId);
178     }
179 
180     private final ICarOccupantZoneCallback mOccupantZoneCallback =
181             new ICarOccupantZoneCallback.Stub() {
182                 @Override
183                 public void onOccupantZoneConfigChanged(int flags) throws RemoteException {
184                     if ((flags & CarOccupantZoneManager.ZONE_CONFIG_CHANGE_FLAG_DISPLAY) != 0
185                             || (flags & CarOccupantZoneManager.ZONE_CONFIG_CHANGE_FLAG_USER) != 0) {
186                         initClusterDisplay();
187                     }
188                 }
189             };
190 
191     @Override
release()192     public void release() {
193         Slogf.d(TAG, "releaseClusterHomeService");
194         mOccupantZoneService.unregisterCallback(mOccupantZoneCallback);
195         mClusterHalService.setCallback(null);
196         mClusterNavigationService.setClusterServiceCallback(null);
197         mClientListeners.kill();
198         mClientNavigationListeners.kill();
199     }
200 
201     @Override
202     @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
dump(IndentingPrintWriter writer)203     public void dump(IndentingPrintWriter writer) {
204         checkHasDumpPermissionGranted(mContext, "dump()");
205         writer.println("*ClusterHomeService*");
206 
207         writer.increaseIndent();
208         writer.printf("mServiceEnabled: %b\n", mServiceEnabled);
209         writer.printf("isLightMode: %b\n", mClusterHalService.isLightMode());
210         writer.printf("mClusterDisplayId: %d\n", mClusterDisplayId);
211         writer.printf("mClusterHomeActivity: %s\n", mClusterHomeActivity);
212         writer.printf("mOnOff: %d\n", mOnOff);
213         writer.printf("mBounds: %s\n", mBounds);
214         writer.printf("mInsets: %s\n", mInsets);
215         writer.printf("mUiType: %d\n", mUiType);
216         writer.printf("mLastIntent: %s\n", mLastIntent);
217         writer.printf("mLastIntentUserId: %d\n", mLastIntentUserId);
218         mClusterHealthMonitor.dump(writer);
219         writer.decreaseIndent();
220     }
221 
222     @Override
223     @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
dumpProto(ProtoOutputStream proto)224     public void dumpProto(ProtoOutputStream proto) {}
225 
226     // ClusterHalEventListener starts
227     @Override
onSwitchUi(int uiType)228     public void onSwitchUi(int uiType) {
229         Slogf.d(TAG, "onSwitchUi: uiType=%d", uiType);
230         int changes = 0;
231         if (mUiType != uiType) {
232             mUiType = uiType;
233             changes |= ClusterHomeManager.CONFIG_UI_TYPE;
234         }
235         sendDisplayState(changes);
236     }
237 
238     @Override
onDisplayState(int onOff, Rect bounds, Insets insets)239     public void onDisplayState(int onOff, Rect bounds, Insets insets) {
240         Slogf.d(TAG, "onDisplayState: onOff=%d, bounds=%s, insets=%s", onOff, bounds, insets);
241         int changes = 0;
242         if (onOff != DONT_CARE && mOnOff != onOff) {
243             mOnOff = onOff;
244             changes |= ClusterHomeManager.CONFIG_DISPLAY_ON_OFF;
245         }
246         if (bounds != null && !mBounds.equals(bounds)) {
247             mBounds = bounds;
248             changes |= ClusterHomeManager.CONFIG_DISPLAY_BOUNDS;
249         }
250         if (insets != null && !mInsets.equals(insets)) {
251             mInsets = insets;
252             changes |= ClusterHomeManager.CONFIG_DISPLAY_INSETS;
253         }
254         sendDisplayState(changes);
255     }
256     // ClusterHalEventListener ends
257 
sendDisplayState(int changes)258     private void sendDisplayState(int changes) {
259         ClusterState state = createClusterState();
260         int n = mClientListeners.beginBroadcast();
261         for (int i = 0; i < n; i++) {
262             IClusterStateListener callback = mClientListeners.getBroadcastItem(i);
263             try {
264                 callback.onClusterStateChanged(state, changes);
265             } catch (RemoteException ignores) {
266                 // ignore
267             }
268         }
269         mClientListeners.finishBroadcast();
270     }
271 
272     // ClusterNavigationServiceCallback starts
273     @Override
onNavigationStateChanged(Bundle bundle)274     public void onNavigationStateChanged(Bundle bundle) {
275         byte[] protoBytes = bundle.getByteArray(NAV_STATE_PROTO_BUNDLE_KEY);
276 
277         sendNavigationState(protoBytes);
278     }
279 
sendNavigationState(byte[] protoBytes)280     private void sendNavigationState(byte[] protoBytes) {
281         final int n = mClientNavigationListeners.beginBroadcast();
282         for (int i = 0; i < n; i++) {
283             IClusterNavigationStateListener callback =
284                     mClientNavigationListeners.getBroadcastItem(i);
285             try {
286                 callback.onNavigationStateChanged(protoBytes);
287             } catch (RemoteException ignores) {
288                 // ignore
289             }
290         }
291         mClientNavigationListeners.finishBroadcast();
292 
293         if (!mClusterHalService.isNavigationStateSupported()) {
294             Slogf.d(TAG, "No Cluster NavigationState HAL property");
295             return;
296         }
297         mClusterHalService.sendNavigationState(protoBytes);
298     }
299 
300     @Override
getInstrumentClusterInfo()301     public CarNavigationInstrumentCluster getInstrumentClusterInfo() {
302         return CarNavigationInstrumentCluster.createCluster(DEFAULT_MIN_UPDATE_INTERVAL_MILLIS);
303     }
304 
305     @Override
notifyNavContextOwnerChanged(ClusterNavigationService.ContextOwner owner)306     public void notifyNavContextOwnerChanged(ClusterNavigationService.ContextOwner owner) {
307         Slogf.d(TAG, "notifyNavContextOwnerChanged: owner=%s", owner);
308         // Sends the empty NavigationStateProto to clear out the last direction
309         // when the app context owner is changed or the navigation is finished.
310         NavigationStateProto emptyProto = NavigationStateProto.newBuilder()
311                 .setServiceStatus(NavigationStateProto.ServiceStatus.NORMAL).build();
312         sendNavigationState(emptyProto.toByteArray());
313     }
314     // ClusterNavigationServiceCallback ends
315 
316     // IClusterHomeService starts
317     @Override
reportState(int uiTypeMain, int uiTypeSub, byte[] uiAvailability)318     public void reportState(int uiTypeMain, int uiTypeSub, byte[] uiAvailability) {
319         Slogf.d(TAG, "reportState: main=%d, sub=%d", uiTypeMain, uiTypeSub);
320         enforcePermission(Car.PERMISSION_CAR_INSTRUMENT_CLUSTER_CONTROL);
321         if (!mServiceEnabled) throw new IllegalStateException("Service is not enabled");
322 
323         mUiType = uiTypeMain;
324         mClusterHalService.reportState(mOnOff, mBounds, mInsets,
325                 uiTypeMain, uiTypeSub, uiAvailability);
326     }
327 
328     @Override
requestDisplay(int uiType)329     public void requestDisplay(int uiType) {
330         Slogf.d(TAG, "requestDisplay: uiType=%d", uiType);
331         enforcePermission(Car.PERMISSION_CAR_INSTRUMENT_CLUSTER_CONTROL);
332         if (!mServiceEnabled) throw new IllegalStateException("Service is not enabled");
333 
334         mClusterHalService.requestDisplay(uiType);
335     }
336 
337     @Override
startFixedActivityModeAsUser(Intent intent, Bundle activityOptionsBundle, int userId)338     public boolean startFixedActivityModeAsUser(Intent intent,
339             Bundle activityOptionsBundle, int userId) {
340         Slogf.d(TAG, "startFixedActivityModeAsUser: intent=%s, userId=%d", intent, userId);
341         enforcePermission(Car.PERMISSION_CAR_INSTRUMENT_CLUSTER_CONTROL);
342         if (!mServiceEnabled) throw new IllegalStateException("Service is not enabled");
343         if (mClusterDisplayId == Display.INVALID_DISPLAY) {
344             Slogf.e(TAG, "Cluster display is not ready.");
345             return false;
346         }
347 
348         ActivityOptions activityOptions = activityOptionsBundle != null
349                 ? createActivityOptions(activityOptionsBundle)
350                 : ActivityOptions.makeBasic();
351         activityOptions.setLaunchDisplayId(mClusterDisplayId);
352         mLastIntent = intent;
353         mLastIntentUserId = userId;
354         return mFixedActivityService.startFixedActivityModeForDisplayAndUser(
355                 intent, activityOptions, mClusterDisplayId, userId);
356     }
357 
358     @Override
stopFixedActivityMode()359     public void stopFixedActivityMode() {
360         enforcePermission(Car.PERMISSION_CAR_INSTRUMENT_CLUSTER_CONTROL);
361         if (!mServiceEnabled) throw new IllegalStateException("Service is not enabled");
362         if (mClusterDisplayId == Display.INVALID_DISPLAY) {
363             Slogf.e(TAG, "Cluster display is not ready.");
364             return;
365         }
366 
367         mFixedActivityService.stopFixedActivityMode(mClusterDisplayId);
368     }
369 
370     @Override
registerClusterStateListener(IClusterStateListener listener)371     public void registerClusterStateListener(IClusterStateListener listener) {
372         enforcePermission(Car.PERMISSION_CAR_INSTRUMENT_CLUSTER_CONTROL);
373         if (!mServiceEnabled) throw new IllegalStateException("Service is not enabled");
374 
375         mClientListeners.register(listener);
376     }
377 
378     @Override
unregisterClusterStateListener(IClusterStateListener listener)379     public void unregisterClusterStateListener(IClusterStateListener listener) {
380         enforcePermission(Car.PERMISSION_CAR_INSTRUMENT_CLUSTER_CONTROL);
381         if (!mServiceEnabled) throw new IllegalStateException("Service is not enabled");
382 
383         mClientListeners.unregister(listener);
384     }
385 
386     @Override
registerClusterNavigationStateListener(IClusterNavigationStateListener listener)387     public void registerClusterNavigationStateListener(IClusterNavigationStateListener listener) {
388         enforcePermission(Car.PERMISSION_CAR_MONITOR_CLUSTER_NAVIGATION_STATE);
389         if (!mServiceEnabled) throw new IllegalStateException("Service is not enabled");
390 
391         mClientNavigationListeners.register(listener);
392     }
393 
394     @Override
unregisterClusterNavigationStateListener(IClusterNavigationStateListener listener)395     public void unregisterClusterNavigationStateListener(IClusterNavigationStateListener listener) {
396         enforcePermission(Car.PERMISSION_CAR_MONITOR_CLUSTER_NAVIGATION_STATE);
397         if (!mServiceEnabled) throw new IllegalStateException("Service is not enabled");
398 
399         mClientNavigationListeners.unregister(listener);
400     }
401 
402     @Override
getClusterState()403     public ClusterState getClusterState() {
404         Slogf.d(TAG, "getClusterState");
405         enforcePermission(Car.PERMISSION_CAR_INSTRUMENT_CLUSTER_CONTROL);
406         if (!mServiceEnabled) throw new IllegalStateException("Service is not enabled");
407         return createClusterState();
408     }
409 
410     @Override
sendHeartbeat(long epochTimeNs, byte[] appMetadata)411     public void sendHeartbeat(long epochTimeNs, byte[] appMetadata) {
412         enforcePermission(Car.PERMISSION_CAR_INSTRUMENT_CLUSTER_CONTROL);
413         mClusterHealthMonitor.sendHeartbeat(epochTimeNs, appMetadata);
414     }
415 
416     @Override
startVisibilityMonitoring(SurfaceControl surface)417     public void startVisibilityMonitoring(SurfaceControl surface) {
418         enforcePermission(Car.PERMISSION_CAR_INSTRUMENT_CLUSTER_CONTROL);
419         mClusterHealthMonitor.startVisibilityMonitoring(surface);
420     }
421 
422     @Override
stopVisibilityMonitoring()423     public void stopVisibilityMonitoring() {
424         enforcePermission(Car.PERMISSION_CAR_INSTRUMENT_CLUSTER_CONTROL);
425         mClusterHealthMonitor.stopVisibilityMonitoring();
426     }
427     // IClusterHomeService ends
428 
enforcePermission(String permissionName)429     private void enforcePermission(String permissionName) {
430         if (mContext.checkCallingOrSelfPermission(permissionName)
431                 != PackageManager.PERMISSION_GRANTED) {
432             throw new SecurityException("requires permission " + permissionName);
433         }
434     }
435 
createClusterState()436     private ClusterState createClusterState() {
437         ClusterState state = new ClusterState();
438         state.on = mOnOff == DISPLAY_ON;
439         state.bounds = mBounds;
440         state.insets = mInsets;
441         state.uiType = mUiType;
442         state.displayId = mClusterDisplayId;
443         return state;
444     }
445 }
446