1 /* 2 * Copyright (C) 2024 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.systembar; 18 19 import static android.car.user.CarUserManager.USER_LIFECYCLE_EVENT_TYPE_SWITCHING; 20 import static android.car.user.CarUserManager.USER_LIFECYCLE_EVENT_TYPE_UNLOCKED; 21 import static android.view.Display.INVALID_DISPLAY; 22 23 import static com.android.car.dockutil.events.DockCompatUtils.isDockSupportedOnDisplay; 24 25 import android.car.Car; 26 import android.car.user.CarUserManager; 27 import android.content.Context; 28 import android.os.Build; 29 import android.os.UserManager; 30 import android.util.Log; 31 32 import androidx.annotation.NonNull; 33 34 import com.android.car.docklib.DockViewController; 35 import com.android.car.docklib.data.DockProtoDataController; 36 import com.android.car.docklib.view.DockView; 37 import com.android.systemui.R; 38 import com.android.systemui.car.CarServiceProvider; 39 import com.android.systemui.car.systembar.element.CarSystemBarElementController; 40 import com.android.systemui.car.systembar.element.CarSystemBarElementStateController; 41 import com.android.systemui.car.systembar.element.CarSystemBarElementStatusBarDisableController; 42 import com.android.systemui.car.systembar.element.layout.CarSystemBarFrameLayout; 43 import com.android.systemui.settings.UserFileManager; 44 import com.android.systemui.settings.UserTracker; 45 46 import dagger.assisted.Assisted; 47 import dagger.assisted.AssistedFactory; 48 import dagger.assisted.AssistedInject; 49 50 import java.io.File; 51 52 /** Wrapper controller class for instantiating {@link DockViewController} inside CarSystemUI */ 53 public class DockViewControllerWrapper extends 54 CarSystemBarElementController<CarSystemBarFrameLayout> { 55 private static final boolean DEBUG = Build.IS_ENG || Build.IS_USERDEBUG; 56 57 private static final String TAG = DockViewControllerWrapper.class.getSimpleName(); 58 59 private final Context mContext; 60 private final UserManager mUserManager; 61 private final UserTracker mUserTracker; 62 private final UserFileManager mUserFileManager; 63 private final CarServiceProvider mCarServiceProvider; 64 65 private DockViewController mDockViewController; 66 private CarUserManager mCarUserManager; 67 private int mActiveUnlockedUserId = -1; 68 69 private final CarUserManager.UserLifecycleListener mUserLifecycleListener = 70 new CarUserManager.UserLifecycleListener() { 71 @Override 72 public void onEvent(@NonNull CarUserManager.UserLifecycleEvent event) { 73 if (event.getUserHandle().isSystem()) { 74 return; 75 } 76 77 switch (event.getEventType()) { 78 case USER_LIFECYCLE_EVENT_TYPE_UNLOCKED -> { 79 if (event.getUserId() == mUserTracker.getUserId()) { 80 setupDock(); 81 } 82 } 83 case USER_LIFECYCLE_EVENT_TYPE_SWITCHING -> { 84 if (event.getPreviousUserId() == mActiveUnlockedUserId) { 85 destroyDock(); 86 } 87 } 88 } 89 } 90 }; 91 92 private final CarServiceProvider.CarServiceOnConnectedListener mCarOnConnectedListener = 93 new CarServiceProvider.CarServiceOnConnectedListener() { 94 @Override 95 public void onConnected(Car car) { 96 mCarUserManager = car.getCarManager(CarUserManager.class); 97 if (mCarUserManager != null) { 98 mCarUserManager.addListener(mContext.getMainExecutor(), 99 mUserLifecycleListener); 100 } 101 } 102 }; 103 104 @AssistedInject DockViewControllerWrapper(@ssisted CarSystemBarFrameLayout view, CarSystemBarElementStatusBarDisableController disableController, CarSystemBarElementStateController stateController, Context context, UserTracker userTracker, UserFileManager userFileManager, CarServiceProvider carServiceProvider)105 DockViewControllerWrapper(@Assisted CarSystemBarFrameLayout view, 106 CarSystemBarElementStatusBarDisableController disableController, 107 CarSystemBarElementStateController stateController, Context context, 108 UserTracker userTracker, UserFileManager userFileManager, 109 CarServiceProvider carServiceProvider) { 110 super(view, disableController, stateController); 111 mContext = context; 112 mUserManager = context.getSystemService(UserManager.class); 113 mUserTracker = userTracker; 114 mUserFileManager = userFileManager; 115 mCarServiceProvider = carServiceProvider; 116 } 117 118 @AssistedFactory 119 public interface Factory extends 120 CarSystemBarElementController.Factory<CarSystemBarFrameLayout, 121 DockViewControllerWrapper> { 122 } 123 124 @Override onViewAttached()125 protected void onViewAttached() { 126 super.onViewAttached(); 127 setupDock(); 128 mCarServiceProvider.addListener(mCarOnConnectedListener); 129 } 130 131 @Override onViewDetached()132 protected void onViewDetached() { 133 super.onViewDetached(); 134 mCarServiceProvider.removeListener(mCarOnConnectedListener); 135 if (mCarUserManager != null) { 136 mCarUserManager.removeListener(mUserLifecycleListener); 137 mCarUserManager = null; 138 } 139 destroyDock(); 140 } 141 createDockViewController(DockView dockView, Context userContext, File dataFile)142 protected DockViewController createDockViewController(DockView dockView, Context userContext, 143 File dataFile) { 144 return new DockViewController(dockView, userContext, dataFile); 145 } 146 setupDock()147 private void setupDock() { 148 if (mDockViewController != null) { 149 if (mDockViewController.getUserContext().getUserId() == mUserTracker.getUserId()) { 150 if (DEBUG) { 151 Log.d(TAG, "Dock already initialized for user: " + mUserTracker.getUserId()); 152 } 153 return; 154 } 155 if (DEBUG) { 156 // This is unexpected. We should not have a leaked instance of 157 // DockViewController for a different user. This indicates a potential 158 // issue with how we're managing the DockViewController lifecycle. 159 Log.w(TAG, "DockViewController: Leaked instance detected for user " 160 + mDockViewController.getUserContext().getUserId() + ". Destroying now."); 161 } 162 mDockViewController.destroy(); 163 } 164 int currentDisplayId = mView.getDisplay() != null ? mView.getDisplay().getDisplayId() 165 : INVALID_DISPLAY; 166 if (!isDockSupportedOnDisplay(mContext, currentDisplayId)) { 167 Log.e(TAG, "Dock cannot be initialised: Tried to launch on unsupported display " 168 + currentDisplayId); 169 return; 170 } 171 DockView dockView = mView.findViewById(R.id.dock); 172 if (dockView == null) { 173 if (DEBUG) { 174 Log.d(TAG, "Dock cannot be initialised: Cannot find dock view"); 175 } 176 return; 177 } 178 if (!mUserManager.isUserUnlocked(mUserTracker.getUserId())) { 179 if (DEBUG) { 180 Log.d(TAG, "Dock cannot be initialised: User not unlocked"); 181 } 182 return; 183 } 184 mDockViewController = createDockViewController( 185 dockView, 186 mUserTracker.getUserContext(), 187 mUserFileManager.getFile( 188 DockProtoDataController.FILE_NAME, 189 mUserTracker.getUserId() 190 ) 191 ); 192 mActiveUnlockedUserId = mUserTracker.getUserId(); 193 } 194 destroyDock()195 private void destroyDock() { 196 if (mDockViewController != null) { 197 mDockViewController.destroy(); 198 mDockViewController = null; 199 } 200 } 201 } 202