• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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