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