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.userpicker; 18 19 import static android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE; 20 import static android.window.OnBackInvokedDispatcher.PRIORITY_DEFAULT; 21 22 import static com.android.systemui.car.userpicker.HeaderState.HEADER_STATE_CHANGE_USER; 23 import static com.android.systemui.car.userpicker.HeaderState.HEADER_STATE_LOGOUT; 24 import static com.android.systemui.car.users.CarSystemUIUserUtil.isMUPANDSystemUI; 25 26 import android.app.Activity; 27 import android.content.Context; 28 import android.content.res.Configuration; 29 import android.graphics.Insets; 30 import android.os.Build; 31 import android.os.Bundle; 32 import android.os.UserManager; 33 import android.util.Log; 34 import android.util.Slog; 35 import android.view.LayoutInflater; 36 import android.view.View; 37 import android.view.ViewGroup; 38 import android.view.Window; 39 import android.view.WindowInsets; 40 import android.view.WindowInsetsController; 41 import android.window.OnBackInvokedCallback; 42 43 import androidx.annotation.NonNull; 44 import androidx.annotation.VisibleForTesting; 45 import androidx.recyclerview.widget.GridLayoutManager; 46 47 import com.android.car.ui.recyclerview.CarUiRecyclerView; 48 import com.android.systemui.Dumpable; 49 import com.android.systemui.R; 50 import com.android.systemui.car.CarServiceProvider; 51 import com.android.systemui.car.systembar.element.CarSystemBarElementInitializer; 52 import com.android.systemui.car.userpicker.UserPickerController.Callbacks; 53 import com.android.systemui.car.userswitcher.UserIconProvider; 54 import com.android.systemui.dump.DumpManager; 55 import com.android.systemui.settings.DisplayTracker; 56 57 import java.io.PrintWriter; 58 import java.util.List; 59 60 import javax.inject.Inject; 61 62 /** 63 * Main activity for user picker. 64 * 65 * <p>This class uses the Trampoline pattern to ensure the activity is executed as user 0. 66 * It has user picker controller object for the executed display, and cleans it up 67 * when the activity is destroyed. 68 */ 69 public class UserPickerActivity extends Activity implements Dumpable { 70 private static final String TAG = UserPickerActivity.class.getSimpleName(); 71 private static final boolean DEBUG = Build.IS_DEBUGGABLE; 72 73 private UserPickerActivityComponent mUserPickerActivityComponent; 74 private boolean mIsDriver; 75 @Inject 76 CarSystemBarElementInitializer mCarSystemBarElementInitializer; 77 @Inject 78 DisplayTracker mDisplayTracker; 79 @Inject 80 DumpManager mDumpManager; 81 82 @VisibleForTesting 83 UserPickerController mController; 84 @VisibleForTesting 85 SnackbarManager mSnackbarManager; 86 @VisibleForTesting 87 DialogManager mDialogManager; 88 @VisibleForTesting 89 UserPickerAdapter mAdapter; 90 @VisibleForTesting 91 CarUiRecyclerView mUserPickerView; 92 @VisibleForTesting 93 View mRootView; 94 @VisibleForTesting 95 View mHeaderBarTextForLogout; 96 @VisibleForTesting 97 View mLogoutButton; 98 @VisibleForTesting 99 View mBackButton; 100 101 private final OnBackInvokedCallback mIgnoreBackCallback = () -> { 102 // Ignore back press. 103 if (DEBUG) Log.d(TAG, "Skip Back"); 104 }; 105 106 @Inject UserPickerActivity( Context context, UserManager userManager, DisplayTracker displayTracker, CarServiceProvider carServiceProvider, UserPickerSharedState userPickerSharedState, UserIconProvider userIconProvider )107 UserPickerActivity( 108 Context context, //application context 109 UserManager userManager, 110 DisplayTracker displayTracker, 111 CarServiceProvider carServiceProvider, 112 UserPickerSharedState userPickerSharedState, 113 UserIconProvider userIconProvider 114 ) { 115 this(); 116 mUserPickerActivityComponent = DaggerUserPickerActivityComponent.builder() 117 .context(context) 118 .userManager(userManager) 119 .carServiceProvider(carServiceProvider) 120 .displayTracker(displayTracker) 121 .userPickerSharedState(userPickerSharedState) 122 .userIconProvider(userIconProvider) 123 .build(); 124 //Component.inject(this) is not working because constructor and activity itself is 125 //scoped to SystemUiScope but the deps below are scoped to UserPickerScope 126 mDialogManager = mUserPickerActivityComponent.dialogManager(); 127 mSnackbarManager = mUserPickerActivityComponent.snackbarManager(); 128 mController = mUserPickerActivityComponent.userPickerController(); 129 } 130 131 @VisibleForTesting UserPickerActivity()132 UserPickerActivity() { 133 super(); 134 } 135 136 private final Callbacks mCallbacks = new Callbacks() { 137 @Override 138 public void onUpdateUsers(List<UserRecord> users) { 139 mAdapter.updateUsers(users); 140 mAdapter.notifyDataSetChanged(); 141 } 142 143 @Override 144 public void onHeaderStateChanged(HeaderState headerState) { 145 setupHeaderBar(headerState); 146 } 147 148 @Override 149 public void onFinishRequested() { 150 finishAndRemoveTask(); 151 } 152 }; 153 154 @Override onCreate(Bundle savedInstanceState)155 protected void onCreate(Bundle savedInstanceState) { 156 if (shouldStartAsSystemUser() 157 && !ActivityHelper.startUserPickerAsUserSystem(this)) { 158 super.onCreate(savedInstanceState); 159 return; 160 } 161 162 if (DEBUG) { 163 Slog.d(TAG, "onCreate: userId=" + getUserId() + " displayId=" + getDisplayId()); 164 } 165 166 super.onCreate(savedInstanceState); 167 setShowWhenLocked(true); 168 requestWindowFeature(Window.FEATURE_NO_TITLE); 169 getOnBackInvokedDispatcher() 170 .registerOnBackInvokedCallback(PRIORITY_DEFAULT, mIgnoreBackCallback); 171 init(); 172 } 173 174 @VisibleForTesting init()175 void init() { 176 mIsDriver = getIsDriver(); 177 LayoutInflater inflater = LayoutInflater.from(this); 178 mRootView = inflater.inflate(R.layout.user_picker, null); 179 if (getWindow() != null) { 180 setContentView(mRootView); 181 initWindow(); 182 } 183 184 initManagers(mRootView); 185 initViews(); 186 initController(); 187 188 mController.onConfigurationChanged(); 189 String dumpableName = TAG + "#" + getDisplayId(); 190 mDumpManager.unregisterDumpable(dumpableName); 191 mDumpManager.registerNormalDumpable(dumpableName, /* module= */ this); 192 } 193 initViews()194 private void initViews() { 195 View powerBtn = mRootView.findViewById(R.id.power_button_icon_view); 196 powerBtn.setOnClickListener(v -> mController.screenOffDisplay()); 197 if (mIsDriver) { 198 powerBtn.setVisibility(View.GONE); 199 } 200 mHeaderBarTextForLogout = mRootView.findViewById(R.id.message); 201 202 mLogoutButton = mRootView.findViewById(R.id.logout_button_icon_view); 203 mLogoutButton.setOnClickListener(v -> mController.logoutUser()); 204 205 mBackButton = mRootView.findViewById(R.id.back_button); 206 mBackButton.setOnClickListener(v -> { 207 finishAndRemoveTask(); 208 }); 209 210 initRecyclerView(); 211 212 // Initialize bar element within the user picker's bottom bar 213 View bottomBar = mRootView.findViewById(R.id.user_picker_bottom_bar); 214 if (bottomBar instanceof ViewGroup) { 215 mCarSystemBarElementInitializer.initializeCarSystemBarElements((ViewGroup) bottomBar); 216 } 217 } 218 initRecyclerView()219 private void initRecyclerView() { 220 int numCols = getResources().getInteger(R.integer.user_fullscreen_switcher_num_col); 221 mUserPickerView = mRootView.findViewById(R.id.user_picker); 222 mUserPickerView.setLayoutManager(new GridLayoutManager(this, numCols)); 223 mAdapter = createUserPickerAdapter(); 224 mUserPickerView.setAdapter(mAdapter); 225 } 226 initWindow()227 private void initWindow() { 228 Window window = getWindow(); 229 window.getDecorView().getRootView().setOnApplyWindowInsetsListener( 230 mOnApplyWindowInsetsListener); 231 232 WindowInsetsController insetsController = window.getInsetsController(); 233 if (insetsController != null) { 234 insetsController.setAnimationsDisabled(true); 235 insetsController.hide(WindowInsets.Type.statusBars() 236 | WindowInsets.Type.navigationBars()); 237 // TODO(b/271139033): Supports passenger display. Currently only systemBars on main 238 // display supports showing transient by swipe. 239 insetsController.setSystemBarsBehavior(BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE); 240 } 241 } 242 243 // Avoid activity resizing due to dismissible system bars. 244 private final View.OnApplyWindowInsetsListener mOnApplyWindowInsetsListener = (v, insets) -> { 245 if (!insets.isVisible(WindowInsets.Type.statusBars())) { 246 Insets statusBarInsets = insets.getInsets(WindowInsets.Type.statusBars()); 247 insets.inset(statusBarInsets); 248 } 249 if (!insets.isVisible(WindowInsets.Type.navigationBars())) { 250 Insets navBarInsets = insets.getInsets(WindowInsets.Type.navigationBars()); 251 insets.inset(navBarInsets); 252 } 253 return insets; 254 }; 255 initManagers(View rootView)256 private void initManagers(View rootView) { 257 mDialogManager.initContextFromView(rootView); 258 mSnackbarManager.setRootView(rootView, R.id.user_picker_bottom_bar); 259 } 260 initController()261 private void initController() { 262 mController.init(mCallbacks, getDisplayId()); 263 } 264 265 @VisibleForTesting createUserPickerAdapter()266 UserPickerAdapter createUserPickerAdapter() { 267 return new UserPickerAdapter(this); 268 } 269 270 @VisibleForTesting shouldStartAsSystemUser()271 boolean shouldStartAsSystemUser() { 272 return true; 273 } 274 275 @VisibleForTesting getIsDriver()276 boolean getIsDriver() { 277 return !isMUPANDSystemUI() && getDisplayId() == mDisplayTracker.getDefaultDisplayId(); 278 } 279 280 @Override onDestroy()281 protected void onDestroy() { 282 if (DEBUG) { 283 Slog.d(TAG, "onDestroy: displayId=" + getDisplayId()); 284 } 285 getOnBackInvokedDispatcher().unregisterOnBackInvokedCallback(mIgnoreBackCallback); 286 if (mController != null) { 287 mController.onDestroy(); 288 } 289 if (mDialogManager != null) { 290 mDialogManager.clearAllDialogs(); 291 } 292 mDumpManager.unregisterDumpable(TAG + "#" + getDisplayId()); 293 294 super.onDestroy(); 295 } 296 297 @Override onConfigurationChanged(@onNull Configuration newConfig)298 public void onConfigurationChanged(@NonNull Configuration newConfig) { 299 super.onConfigurationChanged(newConfig); 300 mAdapter.onConfigurationChanged(); 301 mController.onConfigurationChanged(); 302 } 303 304 @VisibleForTesting setupHeaderBar(HeaderState headerState)305 void setupHeaderBar(HeaderState headerState) { 306 int state = headerState.getState(); 307 switch (state) { 308 case HEADER_STATE_LOGOUT: 309 mHeaderBarTextForLogout.setVisibility(View.VISIBLE); 310 mBackButton.setVisibility(View.GONE); 311 mLogoutButton.setVisibility(View.GONE); 312 break; 313 case HEADER_STATE_CHANGE_USER: 314 mHeaderBarTextForLogout.setVisibility(View.GONE); 315 mBackButton.setVisibility(View.VISIBLE); 316 if (!mIsDriver) { 317 mLogoutButton.setVisibility(View.VISIBLE); 318 } 319 break; 320 } 321 } 322 323 @Override dump(@onNull PrintWriter pw, @NonNull String[] args)324 public void dump(@NonNull PrintWriter pw, @NonNull String[] args) { 325 if (mController != null) { 326 mController.dump(pw); 327 } 328 if (mUserPickerView != null && mUserPickerView.getAdapter() != null) { 329 ((UserPickerAdapter) mUserPickerView.getAdapter()).dump(pw); 330 } 331 } 332 } 333