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