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.statusicon.ui.UserPickerReadOnlyIconsController; 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 UserPickerReadOnlyIconsController mUserPickerReadOnlyIconsController; 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 ViewGroup statusIconContainer = mRootView 213 .findViewById(R.id.user_picker_status_icon_container); 214 if (statusIconContainer != null && mUserPickerReadOnlyIconsController != null) { 215 mUserPickerReadOnlyIconsController.addIconViews(statusIconContainer, 216 /* shouldAttachPanel= */ false); 217 } 218 } 219 initRecyclerView()220 private void initRecyclerView() { 221 int numCols = getResources().getInteger(R.integer.user_fullscreen_switcher_num_col); 222 mUserPickerView = mRootView.findViewById(R.id.user_picker); 223 mUserPickerView.setLayoutManager(new GridLayoutManager(this, numCols)); 224 mAdapter = createUserPickerAdapter(); 225 mUserPickerView.setAdapter(mAdapter); 226 } 227 initWindow()228 private void initWindow() { 229 Window window = getWindow(); 230 WindowInsetsController insetsController = window.getInsetsController(); 231 if (insetsController != null) { 232 insetsController.setAnimationsDisabled(true); 233 insetsController.hide(WindowInsets.Type.statusBars() 234 | WindowInsets.Type.navigationBars()); 235 } 236 } 237 initManagers(View rootView)238 private void initManagers(View rootView) { 239 mDialogManager.initContextFromView(rootView); 240 mSnackbarManager.setRootView(rootView, R.id.user_picker_bottom_bar); 241 } 242 initController()243 private void initController() { 244 mController.init(mCallbacks, getDisplayId()); 245 } 246 247 @VisibleForTesting createUserPickerAdapter()248 UserPickerAdapter createUserPickerAdapter() { 249 return new UserPickerAdapter(this); 250 } 251 252 @VisibleForTesting shouldStartAsSystemUser()253 boolean shouldStartAsSystemUser() { 254 return true; 255 } 256 257 @VisibleForTesting getIsDriver()258 boolean getIsDriver() { 259 return !isMUPANDSystemUI() && getDisplayId() == mDisplayTracker.getDefaultDisplayId(); 260 } 261 262 @Override onStop()263 protected void onStop() { 264 Window window = getWindow(); 265 WindowManager.LayoutParams attrs = window.getAttributes(); 266 attrs.privateFlags &= ~SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS; 267 window.setAttributes(attrs); 268 269 super.onStop(); 270 } 271 272 @Override onDestroy()273 protected void onDestroy() { 274 if (DEBUG) { 275 Slog.d(TAG, "onDestroy: displayId=" + getDisplayId()); 276 } 277 getOnBackInvokedDispatcher().unregisterOnBackInvokedCallback(mIgnoreBackCallback); 278 if (mController != null) { 279 mController.onDestroy(); 280 } 281 if (mDialogManager != null) { 282 mDialogManager.clearAllDialogs(); 283 } 284 mDumpManager.unregisterDumpable(TAG + "#" + getDisplayId()); 285 286 super.onDestroy(); 287 } 288 289 @Override onConfigurationChanged(@onNull Configuration newConfig)290 public void onConfigurationChanged(@NonNull Configuration newConfig) { 291 super.onConfigurationChanged(newConfig); 292 mAdapter.onConfigurationChanged(); 293 mController.onConfigurationChanged(); 294 } 295 296 @VisibleForTesting setupHeaderBar(HeaderState headerState)297 void setupHeaderBar(HeaderState headerState) { 298 int state = headerState.getState(); 299 switch (state) { 300 case HEADER_STATE_LOGOUT: 301 mHeaderBarTextForLogout.setVisibility(View.VISIBLE); 302 mBackButton.setVisibility(View.GONE); 303 mLogoutButton.setVisibility(View.GONE); 304 break; 305 case HEADER_STATE_CHANGE_USER: 306 mHeaderBarTextForLogout.setVisibility(View.GONE); 307 mBackButton.setVisibility(View.VISIBLE); 308 if (!mIsDriver) { 309 mLogoutButton.setVisibility(View.VISIBLE); 310 } 311 break; 312 } 313 } 314 315 @Override dump(@onNull PrintWriter pw, @NonNull String[] args)316 public void dump(@NonNull PrintWriter pw, @NonNull String[] args) { 317 if (mController != null) { 318 mController.dump(pw); 319 } 320 if (mUserPickerView != null && mUserPickerView.getAdapter() != null) { 321 ((UserPickerAdapter) mUserPickerView.getAdapter()).dump(pw); 322 } 323 } 324 } 325