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.qc;
18 
19 import static android.os.UserHandle.USER_NULL;
20 import static android.view.WindowInsets.Type.statusBars;
21 
22 import android.app.ActivityManager;
23 import android.app.AlertDialog;
24 import android.app.IActivityManager;
25 import android.car.CarOccupantZoneManager;
26 import android.car.app.CarActivityManager;
27 import android.content.Context;
28 import android.content.DialogInterface;
29 import android.content.Intent;
30 import android.os.RemoteException;
31 import android.util.Log;
32 import android.view.Display;
33 import android.view.Window;
34 import android.view.WindowManager;
35 
36 import androidx.annotation.Nullable;
37 import androidx.annotation.VisibleForTesting;
38 
39 import com.android.systemui.R;
40 import com.android.systemui.car.CarServiceProvider;
41 import com.android.systemui.car.systembar.element.CarSystemBarElementController;
42 import com.android.systemui.car.systembar.element.CarSystemBarElementStateController;
43 import com.android.systemui.car.systembar.element.CarSystemBarElementStatusBarDisableController;
44 import com.android.systemui.settings.UserTracker;
45 
46 import dagger.assisted.Assisted;
47 import dagger.assisted.AssistedFactory;
48 import dagger.assisted.AssistedInject;
49 
50 import java.util.List;
51 
52 /**
53  * One of {@link QCFooterView} for quick control panels, which logs out the user.
54  */
55 
56 public class QCLogoutButtonController extends QCFooterViewController {
57     private static final String TAG = QCUserPickerButtonController.class.getSimpleName();
58 
59     private final Context mContext;
60     private final UserTracker mUserTracker;
61     private final CarServiceProvider mCarServiceProvider;
62 
63     private CarActivityManager mCarActivityManager;
64     private CarOccupantZoneManager mCarOccupantZoneManager;
65 
66     private float mConfirmLogoutDialogDimValue = -1.0f;
67 
68     private final CarServiceProvider.CarServiceOnConnectedListener mCarServiceLifecycleListener =
69             car -> {
70                 mCarActivityManager = car.getCarManager(CarActivityManager.class);
71                 mCarOccupantZoneManager = car.getCarManager(CarOccupantZoneManager.class);
72             };
73 
74     private final DialogInterface.OnClickListener mOnDialogClickListener = (dialog, which) -> {
75         if (which == DialogInterface.BUTTON_POSITIVE) {
76             dialog.dismiss();
77             logoutUser();
78         } else if (which == DialogInterface.BUTTON_NEGATIVE) {
79             dialog.dismiss();
80         }
81     };
82 
83     @AssistedInject
QCLogoutButtonController(@ssisted QCFooterView view, CarSystemBarElementStatusBarDisableController disableController, CarSystemBarElementStateController stateController, Context context, UserTracker userTracker, CarServiceProvider carServiceProvider)84     protected QCLogoutButtonController(@Assisted QCFooterView view,
85             CarSystemBarElementStatusBarDisableController disableController,
86             CarSystemBarElementStateController stateController, Context context,
87             UserTracker userTracker, CarServiceProvider carServiceProvider) {
88         super(view, disableController, stateController, context, userTracker);
89         mContext = context;
90         mUserTracker = userTracker;
91         mCarServiceProvider = carServiceProvider;
92     }
93 
94     @AssistedFactory
95     public interface Factory extends
96             CarSystemBarElementController.Factory<QCFooterView, QCLogoutButtonController> {}
97 
98     @Override
onInit()99     protected void onInit() {
100         super.onInit();
101         mView.setOnClickListener(v -> showDialog());
102     }
103 
104     @Override
onViewAttached()105     protected void onViewAttached() {
106         super.onViewAttached();
107         mCarServiceProvider.addListener(mCarServiceLifecycleListener);
108     }
109 
110     @Override
onViewDetached()111     protected void onViewDetached() {
112         super.onViewDetached();
113         mCarServiceProvider.removeListener(mCarServiceLifecycleListener);
114     }
115 
116 
showDialog()117     private void showDialog() {
118         Intent intent = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
119         mContext.sendBroadcastAsUser(intent, mUserTracker.getUserHandle());
120         AlertDialog dialog = createDialog();
121 
122         // Sets window flags for the SysUI dialog
123         applyCarSysUIDialogFlags(dialog);
124         dialog.show();
125     }
126 
127     @VisibleForTesting
createDialog()128     AlertDialog createDialog() {
129         Context context = getContext().createWindowContext(getContext().getDisplay(),
130                 WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL, null);
131         return new AlertDialog.Builder(context,
132                 com.android.internal.R.style.Theme_DeviceDefault_Dialog_Alert)
133                 .setTitle(R.string.user_logout_title)
134                 .setMessage(R.string.user_logout_message)
135                 .setNegativeButton(android.R.string.cancel, mOnDialogClickListener)
136                 .setPositiveButton(R.string.user_logout, mOnDialogClickListener)
137                 .create();
138     }
139 
applyCarSysUIDialogFlags(AlertDialog dialog)140     private void applyCarSysUIDialogFlags(AlertDialog dialog) {
141         final Window window = dialog.getWindow();
142         window.setType(WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL);
143         window.addFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
144         window.getAttributes().setFitInsetsTypes(
145                 window.getAttributes().getFitInsetsTypes() & ~statusBars());
146         if (mConfirmLogoutDialogDimValue < 0) {
147             mConfirmLogoutDialogDimValue = getContext().getResources().getFloat(
148                     R.dimen.confirm_logout_dialog_dim);
149         }
150         window.setDimAmount(mConfirmLogoutDialogDimValue);
151     }
152 
logoutUser()153     private void logoutUser() {
154         Intent intent = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
155         mContext.sendBroadcastAsUser(intent, mUserTracker.getUserHandle());
156 
157         if (mUserTracker != null) {
158             int userId = mUserTracker.getUserId();
159             if (userId != USER_NULL) {
160                 int displayId = getContext().getDisplayId();
161                 CarOccupantZoneManager.OccupantZoneInfo zoneInfo = getOccupantZoneForDisplayId(
162                         displayId);
163                 if (zoneInfo == null) {
164                     Log.e(TAG,
165                             "Cannot find occupant zone info associated with display " + displayId);
166                     return;
167                 }
168 
169                 // Unassign the user from the occupant zone.
170                 // TODO(b/253264316): See if we can move it to CarUserService.
171                 int result = mCarOccupantZoneManager.unassignOccupantZone(zoneInfo);
172                 if (result != android.car.CarOccupantZoneManager.USER_ASSIGNMENT_RESULT_OK) {
173                     Log.e(TAG, "failed to unassign user " + userId + " from occupant zone "
174                             + zoneInfo.zoneId);
175                     return;
176                 }
177 
178                 IActivityManager am = ActivityManager.getService();
179                 try {
180                     // Use stopUserWithDelayedLocking instead of stopUser
181                     // to make the call more efficient.
182                     am.stopUserWithDelayedLocking(userId, /* callback= */ null);
183                 } catch (RemoteException e) {
184                     Log.e(TAG, "Cannot stop user " + userId);
185                     return;
186                 }
187                 openUserPicker();
188             }
189         }
190     }
191 
192     // TODO(b/248608281): Use API from CarOccupantZoneManager for convenience.
193     @Nullable
getOccupantZoneForDisplayId(int displayId)194     private CarOccupantZoneManager.OccupantZoneInfo getOccupantZoneForDisplayId(int displayId) {
195         List<CarOccupantZoneManager.OccupantZoneInfo> occupantZoneInfos =
196                 mCarOccupantZoneManager.getAllOccupantZones();
197         for (int index = 0; index < occupantZoneInfos.size(); index++) {
198             CarOccupantZoneManager.OccupantZoneInfo occupantZoneInfo = occupantZoneInfos.get(index);
199             List<Display> displays = mCarOccupantZoneManager.getAllDisplaysForOccupant(
200                     occupantZoneInfo);
201             for (int displayIndex = 0; displayIndex < displays.size(); displayIndex++) {
202                 if (displays.get(displayIndex).getDisplayId() == displayId) {
203                     return occupantZoneInfo;
204                 }
205             }
206         }
207         return null;
208     }
209 
openUserPicker()210     private void openUserPicker() {
211         if (mCarActivityManager != null) {
212             mCarActivityManager.startUserPickerOnDisplay(getContext().getDisplayId());
213         }
214     }
215 
216     @VisibleForTesting
getOnDialogClickListener()217     protected DialogInterface.OnClickListener getOnDialogClickListener() {
218         return mOnDialogClickListener;
219     }
220 }
221