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.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS; 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.os.Bundle; 30 import android.util.Log; 31 import android.util.Slog; 32 import android.view.LayoutInflater; 33 import android.view.View; 34 import android.view.ViewGroup; 35 import android.view.Window; 36 import android.view.WindowInsets; 37 import android.view.WindowInsetsController; 38 import android.view.WindowManager; 39 import android.window.OnBackInvokedCallback; 40 41 import androidx.annotation.NonNull; 42 import androidx.annotation.VisibleForTesting; 43 import androidx.recyclerview.widget.GridLayoutManager; 44 45 import com.android.car.ui.recyclerview.CarUiRecyclerView; 46 import com.android.systemui.Dumpable; 47 import com.android.systemui.R; 48 import com.android.systemui.car.CarServiceProvider; 49 import com.android.systemui.car.systembar.element.CarSystemBarElementInitializer; 50 import com.android.systemui.car.userpicker.UserPickerController.Callbacks; 51 import com.android.systemui.dump.DumpManager; 52 import com.android.systemui.settings.DisplayTracker; 53 54 import java.io.PrintWriter; 55 import java.util.List; 56 57 import javax.inject.Inject; 58 59 /** 60 * Main activity for user picker. 61 * 62 * <p>This class uses the Trampoline pattern to ensure the activity is executed as user 0. 63 * It has user picker controller object for the executed display, and cleans it up 64 * when the activity is destroyed. 65 */ 66 public class UserPickerActivity extends Activity implements Dumpable { 67 private static final String TAG = UserPickerActivity.class.getSimpleName(); 68 private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); 69 70 private UserPickerActivityComponent mUserPickerActivityComponent; 71 private boolean mIsDriver; 72 73 @Inject 74 CarSystemBarElementInitializer mCarSystemBarElementInitializer; 75 @Inject 76 DisplayTracker mDisplayTracker; 77 @Inject 78 DumpManager mDumpManager; 79 80 @VisibleForTesting 81 UserPickerController mController; 82 @VisibleForTesting 83 SnackbarManager mSnackbarManager; 84 @VisibleForTesting 85 DialogManager mDialogManager; 86 @VisibleForTesting 87 UserPickerAdapter mAdapter; 88 @VisibleForTesting 89 CarUiRecyclerView mUserPickerView; 90 @VisibleForTesting 91 View mRootView; 92 @VisibleForTesting 93 View mHeaderBarTextForLogout; 94 @VisibleForTesting 95 View mLogoutButton; 96 @VisibleForTesting 97 View mBackButton; 98 99 private final OnBackInvokedCallback mIgnoreBackCallback = () -> { 100 // Ignore back press. 101 if (DEBUG) Log.d(TAG, "Skip Back"); 102 }; 103 104 @Inject UserPickerActivity( Context context, DisplayTracker displayTracker, CarServiceProvider carServiceProvider, UserPickerSharedState userPickerSharedState )105 UserPickerActivity( 106 Context context, //application context 107 DisplayTracker displayTracker, 108 CarServiceProvider carServiceProvider, 109 UserPickerSharedState userPickerSharedState 110 ) { 111 this(); 112 mUserPickerActivityComponent = DaggerUserPickerActivityComponent.builder() 113 .context(context) 114 .carServiceProvider(carServiceProvider) 115 .displayTracker(displayTracker) 116 .userPickerSharedState(userPickerSharedState) 117 .build(); 118 //Component.inject(this) is not working because constructor and activity itself is 119 //scoped to SystemUiScope but the deps below are scoped to UserPickerScope 120 mDialogManager = mUserPickerActivityComponent.dialogManager(); 121 mSnackbarManager = mUserPickerActivityComponent.snackbarManager(); 122 mController = mUserPickerActivityComponent.userPickerController(); 123 } 124 125 @VisibleForTesting UserPickerActivity()126 UserPickerActivity() { 127 super(); 128 } 129 130 private final Callbacks mCallbacks = new Callbacks() { 131 @Override 132 public void onUpdateUsers(List<UserRecord> users) { 133 mAdapter.updateUsers(users); 134 mAdapter.notifyDataSetChanged(); 135 } 136 137 @Override 138 public void onHeaderStateChanged(HeaderState headerState) { 139 setupHeaderBar(headerState); 140 } 141 142 @Override 143 public void onFinishRequested() { 144 finishAndRemoveTask(); 145 } 146 }; 147 148 @Override onCreate(Bundle savedInstanceState)149 protected void onCreate(Bundle savedInstanceState) { 150 if (shouldStartAsSystemUser() 151 && !ActivityHelper.startUserPickerAsUserSystem(this)) { 152 super.onCreate(savedInstanceState); 153 return; 154 } 155 156 if (DEBUG) { 157 Slog.d(TAG, "onCreate: userId=" + getUserId() + " displayId=" + getDisplayId()); 158 } 159 160 super.onCreate(savedInstanceState); 161 setShowWhenLocked(true); 162 requestWindowFeature(Window.FEATURE_NO_TITLE); 163 getOnBackInvokedDispatcher() 164 .registerOnBackInvokedCallback(PRIORITY_DEFAULT, mIgnoreBackCallback); 165 init(); 166 } 167 168 @VisibleForTesting init()169 void init() { 170 mIsDriver = getIsDriver(); 171 LayoutInflater inflater = LayoutInflater.from(this); 172 mRootView = inflater.inflate(R.layout.user_picker, null); 173 if (getWindow() != null) { 174 setContentView(mRootView); 175 initWindow(); 176 } 177 178 initManagers(mRootView); 179 initViews(); 180 initController(); 181 182 mController.onConfigurationChanged(); 183 String dumpableName = TAG + "#" + getDisplayId(); 184 mDumpManager.unregisterDumpable(dumpableName); 185 mDumpManager.registerNormalDumpable(dumpableName, /* module= */ this); 186 } 187 188 @Override onStart()189 protected void onStart() { 190 super.onStart(); 191 getWindow().addSystemFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS); 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 WindowInsetsController insetsController = window.getInsetsController(); 230 if (insetsController != null) { 231 insetsController.setAnimationsDisabled(true); 232 insetsController.hide(WindowInsets.Type.statusBars() 233 | WindowInsets.Type.navigationBars()); 234 } 235 } 236 initManagers(View rootView)237 private void initManagers(View rootView) { 238 mDialogManager.initContextFromView(rootView); 239 mSnackbarManager.setRootView(rootView, R.id.user_picker_bottom_bar); 240 } 241 initController()242 private void initController() { 243 mController.init(mCallbacks, getDisplayId()); 244 } 245 246 @VisibleForTesting createUserPickerAdapter()247 UserPickerAdapter createUserPickerAdapter() { 248 return new UserPickerAdapter(this); 249 } 250 251 @VisibleForTesting shouldStartAsSystemUser()252 boolean shouldStartAsSystemUser() { 253 return true; 254 } 255 256 @VisibleForTesting getIsDriver()257 boolean getIsDriver() { 258 return !isMUPANDSystemUI() && getDisplayId() == mDisplayTracker.getDefaultDisplayId(); 259 } 260 261 @Override onStop()262 protected void onStop() { 263 Window window = getWindow(); 264 WindowManager.LayoutParams attrs = window.getAttributes(); 265 attrs.privateFlags &= ~SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS; 266 window.setAttributes(attrs); 267 268 super.onStop(); 269 } 270 271 @Override onDestroy()272 protected void onDestroy() { 273 if (DEBUG) { 274 Slog.d(TAG, "onDestroy: displayId=" + getDisplayId()); 275 } 276 getOnBackInvokedDispatcher().unregisterOnBackInvokedCallback(mIgnoreBackCallback); 277 if (mController != null) { 278 mController.onDestroy(); 279 } 280 if (mDialogManager != null) { 281 mDialogManager.clearAllDialogs(); 282 } 283 mDumpManager.unregisterDumpable(TAG + "#" + getDisplayId()); 284 285 super.onDestroy(); 286 } 287 288 @Override onConfigurationChanged(@onNull Configuration newConfig)289 public void onConfigurationChanged(@NonNull Configuration newConfig) { 290 super.onConfigurationChanged(newConfig); 291 mAdapter.onConfigurationChanged(); 292 mController.onConfigurationChanged(); 293 } 294 295 @VisibleForTesting setupHeaderBar(HeaderState headerState)296 void setupHeaderBar(HeaderState headerState) { 297 int state = headerState.getState(); 298 switch (state) { 299 case HEADER_STATE_LOGOUT: 300 mHeaderBarTextForLogout.setVisibility(View.VISIBLE); 301 mBackButton.setVisibility(View.GONE); 302 mLogoutButton.setVisibility(View.GONE); 303 break; 304 case HEADER_STATE_CHANGE_USER: 305 mHeaderBarTextForLogout.setVisibility(View.GONE); 306 mBackButton.setVisibility(View.VISIBLE); 307 if (!mIsDriver) { 308 mLogoutButton.setVisibility(View.VISIBLE); 309 } 310 break; 311 } 312 } 313 314 @Override dump(@onNull PrintWriter pw, @NonNull String[] args)315 public void dump(@NonNull PrintWriter pw, @NonNull String[] args) { 316 if (mController != null) { 317 mController.dump(pw); 318 } 319 if (mUserPickerView != null && mUserPickerView.getAdapter() != null) { 320 ((UserPickerAdapter) mUserPickerView.getAdapter()).dump(pw); 321 } 322 } 323 } 324