1 /*
2  * Copyright (C) 2023 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.systemui.car.users;
18 
19 import static android.car.CarOccupantZoneManager.INVALID_USER_ID;
20 import static android.car.user.CarUserManager.USER_LIFECYCLE_EVENT_TYPE_INVISIBLE;
21 import static android.car.user.CarUserManager.USER_LIFECYCLE_EVENT_TYPE_STARTING;
22 import static android.car.user.CarUserManager.USER_LIFECYCLE_EVENT_TYPE_VISIBLE;
23 
24 import android.annotation.UserIdInt;
25 import android.app.IActivityManager;
26 import android.car.Car;
27 import android.car.CarOccupantZoneManager;
28 import android.car.user.CarUserManager;
29 import android.car.user.UserLifecycleEventFilter;
30 import android.content.Context;
31 import android.os.Handler;
32 import android.os.UserHandle;
33 import android.os.UserManager;
34 import android.util.Log;
35 import android.view.Display;
36 
37 import com.android.systemui.InitController;
38 import com.android.systemui.car.CarServiceProvider;
39 import com.android.systemui.dump.DumpManager;
40 import com.android.systemui.flags.FeatureFlagsClassic;
41 import com.android.systemui.settings.UserTrackerImpl;
42 
43 import java.util.List;
44 import java.util.concurrent.Executor;
45 import java.util.concurrent.Executors;
46 
47 import javax.inject.Provider;
48 
49 import kotlinx.coroutines.CoroutineDispatcher;
50 import kotlinx.coroutines.CoroutineScope;
51 
52 /**
53  * Custom user tracking class extended from {@link UserTrackerImpl} specifically for
54  * the system user (user 0) running in a MUPAND configuration. This tracker will behave
55  * as if the passenger user running on the default display is the foreground user and
56  * handle switching accordingly.
57  */
58 public class CarMUPANDUserTrackerImpl extends UserTrackerImpl {
59     private static final String TAG = CarMUPANDUserTrackerImpl.class.getName();
60     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
61 
62     private final CarServiceProvider mCarServiceProvider;
63     private final Executor mCarUserManagerCallbackExecutor;
64     private CarOccupantZoneManager mCarOccupantZoneManager;
65     private boolean mIsPostInit;
66 
67     private final UserLifecycleEventFilter mFilter = new UserLifecycleEventFilter.Builder()
68             .addEventType(USER_LIFECYCLE_EVENT_TYPE_STARTING)
69             .addEventType(USER_LIFECYCLE_EVENT_TYPE_VISIBLE)
70             .addEventType(USER_LIFECYCLE_EVENT_TYPE_INVISIBLE)
71             .build();
72 
73     private final CarUserManager.UserLifecycleListener mUserLifecycleListener = event -> {
74         int eventType = event.getEventType();
75         if (DEBUG) {
76             Log.d(TAG, "UserLifeCycleEvent eventType=" + eventType);
77         }
78         int userId = event.getUserId();
79         if (eventType == USER_LIFECYCLE_EVENT_TYPE_INVISIBLE) {
80             if (userId == getUserId() && userId != UserHandle.USER_SYSTEM) {
81                 // User has logged out - switch back to the system user
82                 if (DEBUG) {
83                     Log.d(TAG, String.format("User %d removed from default display"
84                                     + " - switching to system user", userId));
85                 }
86                 performUserSwitch(UserHandle.USER_SYSTEM);
87             }
88             return;
89         }
90 
91         if (getDisplayIdForUser(userId) == Display.DEFAULT_DISPLAY) {
92             if (DEBUG) {
93                 Log.d(TAG, String.format("Assigning user %d to default display SysUI", userId));
94             }
95             // Non-foreground user running on default display will be assigned to system user SysUI
96             performUserSwitch(userId);
97         }
98     };
99 
CarMUPANDUserTrackerImpl(Context context, Provider<FeatureFlagsClassic> featureFlagsClassic, UserManager userManager, IActivityManager iActivityManager, DumpManager dumpManager, CoroutineScope appScope, CoroutineDispatcher backgroundContext, Handler backgroundHandler, CarServiceProvider carServiceProvider, InitController initController)100     public CarMUPANDUserTrackerImpl(Context context,
101             Provider<FeatureFlagsClassic> featureFlagsClassic, UserManager userManager,
102             IActivityManager iActivityManager, DumpManager dumpManager,
103             CoroutineScope appScope, CoroutineDispatcher backgroundContext,
104             Handler backgroundHandler, CarServiceProvider carServiceProvider,
105             InitController initController) {
106         super(context, featureFlagsClassic, userManager, iActivityManager, dumpManager, appScope,
107                 backgroundContext, backgroundHandler);
108         mCarUserManagerCallbackExecutor = Executors.newSingleThreadExecutor();
109         mCarServiceProvider = carServiceProvider;
110 
111         initController.addPostInitTask(() -> {
112             maybePerformInitialUserSwitch();
113             mIsPostInit = true;
114         });
115     }
116 
117     @Override
initialize(int startingUser)118     public void initialize(int startingUser) {
119         if (getInitialized()) {
120             return;
121         }
122         super.initialize(startingUser);
123         mCarServiceProvider.addListener(mCarServiceOnConnectedListener);
124     }
125 
getDisplayIdForUser(@serIdInt int userId)126     private int getDisplayIdForUser(@UserIdInt int userId) {
127         if (mCarOccupantZoneManager == null) {
128             return Display.INVALID_DISPLAY;
129         }
130 
131         List<CarOccupantZoneManager.OccupantZoneInfo> occupantZoneInfos =
132                 mCarOccupantZoneManager.getAllOccupantZones();
133         for (int i = 0; i < occupantZoneInfos.size(); i++) {
134             CarOccupantZoneManager.OccupantZoneInfo zoneInfo = occupantZoneInfos.get(i);
135             int zoneUserId = mCarOccupantZoneManager.getUserForOccupant(zoneInfo);
136             if (zoneUserId == userId) {
137                 Display d = mCarOccupantZoneManager.getDisplayForOccupant(zoneInfo,
138                         CarOccupantZoneManager.DISPLAY_TYPE_MAIN);
139                 return d != null ? d.getDisplayId() : Display.INVALID_DISPLAY;
140             }
141         }
142         return Display.INVALID_DISPLAY;
143     }
144 
performUserSwitch(int userId)145     private void performUserSwitch(int userId) {
146         if (userId == getUserId()) {
147             if (DEBUG) {
148                 Log.d(TAG, String.format("User %d already assigned to display", userId));
149             }
150             return;
151         }
152         handleBeforeUserSwitching(userId);
153         handleUserSwitching(userId);
154         handleUserSwitchComplete(userId);
155     }
156 
maybePerformInitialUserSwitch()157     private void maybePerformInitialUserSwitch() {
158         if (mCarOccupantZoneManager == null) {
159             return;
160         }
161         int userId = mCarOccupantZoneManager.getUserForDisplayId(
162                 Display.DEFAULT_DISPLAY);
163         if (userId != INVALID_USER_ID) {
164             mCarUserManagerCallbackExecutor.execute(() -> performUserSwitch(userId));
165         }
166     }
167 
168     private final CarServiceProvider.CarServiceOnConnectedListener mCarServiceOnConnectedListener =
169             new CarServiceProvider.CarServiceOnConnectedListener() {
170                 @Override
171                 public void onConnected(Car car) {
172                     mCarOccupantZoneManager = car.getCarManager(CarOccupantZoneManager.class);
173                     if (mIsPostInit) {
174                         // Car connected after SystemUI initialization completed - check to see
175                         // if an initial user switch is necessary.
176                         maybePerformInitialUserSwitch();
177                     }
178 
179                     CarUserManager carUserManager = car.getCarManager(CarUserManager.class);
180                     if (carUserManager != null) {
181                         carUserManager.addListener(mCarUserManagerCallbackExecutor, mFilter,
182                                 mUserLifecycleListener);
183                     }
184                 }
185             };
186 }
187