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