1 /*
2  * Copyright (C) 2022 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;
18 
19 import android.annotation.SdkConstant;
20 import android.annotation.SdkConstant.SdkConstantType;
21 import android.app.AlertDialog;
22 import android.content.BroadcastReceiver;
23 import android.content.Context;
24 import android.content.DialogInterface;
25 import android.content.Intent;
26 import android.content.IntentFilter;
27 import android.content.pm.UserInfo;
28 import android.content.res.Resources;
29 import android.os.UserHandle;
30 
31 import com.android.internal.logging.UiEventLogger;
32 import com.android.systemui.broadcast.BroadcastDispatcher;
33 import com.android.systemui.dagger.qualifiers.Main;
34 import com.android.systemui.qs.QSUserSwitcherEvent;
35 import com.android.systemui.settings.UserTracker;
36 import com.android.systemui.statusbar.phone.SystemUIDialog;
37 import com.android.systemui.statusbar.policy.UserSwitcherController;
38 
39 import dagger.assisted.Assisted;
40 import dagger.assisted.AssistedFactory;
41 import dagger.assisted.AssistedInject;
42 
43 import javax.inject.Inject;
44 
45 /**
46  * Manages handling of guest session persistent notification
47  * and actions to reset guest or exit guest session
48  */
49 public final class GuestResetOrExitSessionReceiver extends BroadcastReceiver {
50 
51     private static final String TAG = GuestResetOrExitSessionReceiver.class.getSimpleName();
52 
53     /**
54      * Broadcast sent to the system when guest user needs to be reset.
55      * This is only sent to registered receivers, not manifest receivers.
56      *
57      * @hide
58      */
59     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
60     public static final String ACTION_GUEST_RESET = "android.intent.action.GUEST_RESET";
61 
62     /**
63      * Broadcast sent to the system when guest user needs to exit.
64      * This is only sent to registered receivers, not manifest receivers.
65      *
66      * @hide
67      */
68     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
69     public static final String ACTION_GUEST_EXIT = "android.intent.action.GUEST_EXIT";
70 
71     public AlertDialog mExitSessionDialog;
72     public AlertDialog mResetSessionDialog;
73     private final UserTracker mUserTracker;
74     private final BroadcastDispatcher mBroadcastDispatcher;
75     private final ResetSessionDialogFactory mResetSessionDialogFactory;
76     private final ExitSessionDialogFactory mExitSessionDialogFactory;
77 
78     @Inject
GuestResetOrExitSessionReceiver(UserTracker userTracker, BroadcastDispatcher broadcastDispatcher, ResetSessionDialogFactory resetSessionDialogFactory, ExitSessionDialogFactory exitSessionDialogFactory)79     public GuestResetOrExitSessionReceiver(UserTracker userTracker,
80             BroadcastDispatcher broadcastDispatcher,
81             ResetSessionDialogFactory resetSessionDialogFactory,
82             ExitSessionDialogFactory exitSessionDialogFactory) {
83         mUserTracker = userTracker;
84         mBroadcastDispatcher = broadcastDispatcher;
85         mResetSessionDialogFactory = resetSessionDialogFactory;
86         mExitSessionDialogFactory = exitSessionDialogFactory;
87     }
88 
89     /**
90      * Register this receiver with the {@link BroadcastDispatcher}
91      */
register()92     public void register() {
93         IntentFilter intentFilter = new IntentFilter();
94         intentFilter.addAction(ACTION_GUEST_RESET);
95         intentFilter.addAction(ACTION_GUEST_EXIT);
96         mBroadcastDispatcher.registerReceiver(this, intentFilter, null /* handler */,
97                                              UserHandle.SYSTEM);
98     }
99 
100     @Override
onReceive(Context context, Intent intent)101     public void onReceive(Context context, Intent intent) {
102         String action = intent.getAction();
103 
104         cancelResetDialog();
105         cancelExitDialog();
106 
107         UserInfo currentUser = mUserTracker.getUserInfo();
108         if (!currentUser.isGuest()) {
109             return;
110         }
111 
112         if (ACTION_GUEST_RESET.equals(action)) {
113             mResetSessionDialog = mResetSessionDialogFactory.create(currentUser.id);
114             mResetSessionDialog.show();
115         } else if (ACTION_GUEST_EXIT.equals(action)) {
116             mExitSessionDialog = mExitSessionDialogFactory.create(
117                     currentUser.isEphemeral(), currentUser.id);
118             mExitSessionDialog.show();
119         }
120     }
121 
cancelResetDialog()122     private void cancelResetDialog() {
123         if (mResetSessionDialog != null && mResetSessionDialog.isShowing()) {
124             mResetSessionDialog.cancel();
125             mResetSessionDialog = null;
126         }
127     }
128 
cancelExitDialog()129     private void cancelExitDialog() {
130         if (mExitSessionDialog != null && mExitSessionDialog.isShowing()) {
131             mExitSessionDialog.cancel();
132             mExitSessionDialog = null;
133         }
134     }
135 
136     /**
137      * Factory class to create guest reset dialog instance
138      *
139      * Dialog shown when asking for confirmation before
140      * reset and restart of guest user.
141      */
142     public static final class ResetSessionDialogFactory {
143         private final SystemUIDialog.Factory mDialogFactory;
144         private final Resources mResources;
145         private final ResetSessionDialogClickListener.Factory mClickListenerFactory;
146 
147         @Inject
ResetSessionDialogFactory( SystemUIDialog.Factory dialogFactory, @Main Resources resources, ResetSessionDialogClickListener.Factory clickListenerFactory)148         public ResetSessionDialogFactory(
149                 SystemUIDialog.Factory dialogFactory,
150                 @Main Resources resources,
151                 ResetSessionDialogClickListener.Factory clickListenerFactory) {
152             mDialogFactory = dialogFactory;
153             mResources = resources;
154             mClickListenerFactory = clickListenerFactory;
155         }
156 
157         /** Create a guest reset dialog instance */
create(int userId)158         public AlertDialog create(int userId) {
159             SystemUIDialog dialog = mDialogFactory.create();
160             ResetSessionDialogClickListener listener = mClickListenerFactory.create(
161                     userId, dialog);
162             dialog.setTitle(com.android.settingslib.R.string.guest_reset_and_restart_dialog_title);
163             dialog.setMessage(mResources.getString(
164                     com.android.settingslib.R.string.guest_reset_and_restart_dialog_message));
165             dialog.setButton(
166                     DialogInterface.BUTTON_NEUTRAL,
167                     mResources.getString(android.R.string.cancel),
168                     listener);
169             dialog.setButton(DialogInterface.BUTTON_POSITIVE,
170                     mResources.getString(
171                             com.android.settingslib.R.string.guest_reset_guest_confirm_button),
172                     listener);
173             dialog.setCanceledOnTouchOutside(false);
174             return dialog;
175         }
176     }
177 
178     public static class ResetSessionDialogClickListener implements DialogInterface.OnClickListener {
179         private final UserSwitcherController mUserSwitcherController;
180         private final UiEventLogger mUiEventLogger;
181         private final int mUserId;
182         private final DialogInterface mDialog;
183 
184         @AssistedFactory
185         public interface Factory {
create(int userId, DialogInterface dialog)186             ResetSessionDialogClickListener create(int userId, DialogInterface dialog);
187         }
188 
189         @AssistedInject
ResetSessionDialogClickListener( UserSwitcherController userSwitcherController, UiEventLogger uiEventLogger, @Assisted int userId, @Assisted DialogInterface dialog )190         public ResetSessionDialogClickListener(
191                 UserSwitcherController userSwitcherController,
192                 UiEventLogger uiEventLogger,
193                 @Assisted int userId,
194                 @Assisted DialogInterface dialog
195         ) {
196             mUserSwitcherController = userSwitcherController;
197             mUiEventLogger = uiEventLogger;
198             mUserId = userId;
199             mDialog = dialog;
200         }
201 
202         @Override
onClick(DialogInterface dialog, int which)203         public void onClick(DialogInterface dialog, int which) {
204             if (which == DialogInterface.BUTTON_POSITIVE) {
205                 mUiEventLogger.log(QSUserSwitcherEvent.QS_USER_GUEST_REMOVE);
206                 mUserSwitcherController.removeGuestUser(mUserId, UserHandle.USER_NULL);
207             } else if (which == DialogInterface.BUTTON_NEUTRAL) {
208                 mDialog.cancel();
209             }
210         }
211     }
212 
213     /**
214      * Dialog shown when asking for confirmation before
215      * exit of guest user.
216      */
217     public static final class ExitSessionDialogFactory {
218         private final SystemUIDialog.Factory mDialogFactory;
219         private final ExitSessionDialogClickListener.Factory mClickListenerFactory;
220         private final Resources mResources;
221 
222         @Inject
ExitSessionDialogFactory( SystemUIDialog.Factory dialogFactory, ExitSessionDialogClickListener.Factory clickListenerFactory, @Main Resources resources)223         public ExitSessionDialogFactory(
224                 SystemUIDialog.Factory dialogFactory,
225                 ExitSessionDialogClickListener.Factory clickListenerFactory,
226                 @Main Resources resources) {
227             mDialogFactory = dialogFactory;
228             mClickListenerFactory = clickListenerFactory;
229             mResources = resources;
230         }
231 
create(boolean isEphemeral, int userId)232         public AlertDialog create(boolean isEphemeral, int userId) {
233             SystemUIDialog dialog = mDialogFactory.create();
234             ExitSessionDialogClickListener clickListener = mClickListenerFactory.create(
235                     isEphemeral, userId, dialog);
236             if (isEphemeral) {
237                 dialog.setTitle(mResources.getString(
238                         com.android.settingslib.R.string.guest_exit_dialog_title));
239                 dialog.setMessage(mResources.getString(
240                         com.android.settingslib.R.string.guest_exit_dialog_message));
241                 dialog.setButton(
242                         DialogInterface.BUTTON_NEUTRAL,
243                         mResources.getString(android.R.string.cancel),
244                         clickListener);
245                 dialog.setButton(
246                         DialogInterface.BUTTON_POSITIVE,
247                         mResources.getString(
248                                 com.android.settingslib.R.string.guest_exit_dialog_button),
249                         clickListener);
250             } else {
251                 dialog.setTitle(mResources.getString(
252                         com.android.settingslib
253                                 .R.string.guest_exit_dialog_title_non_ephemeral));
254                 dialog.setMessage(mResources.getString(
255                         com.android.settingslib
256                                 .R.string.guest_exit_dialog_message_non_ephemeral));
257                 dialog.setButton(
258                         DialogInterface.BUTTON_NEUTRAL,
259                         mResources.getString(android.R.string.cancel),
260                         clickListener);
261                 dialog.setButton(
262                         DialogInterface.BUTTON_NEGATIVE,
263                         mResources.getString(
264                                 com.android.settingslib.R.string.guest_exit_clear_data_button),
265                         clickListener);
266                 dialog.setButton(
267                         DialogInterface.BUTTON_POSITIVE,
268                         mResources.getString(
269                                 com.android.settingslib.R.string.guest_exit_save_data_button),
270                         clickListener);
271             }
272             dialog.setCanceledOnTouchOutside(false);
273 
274             return dialog;
275         }
276 
277     }
278 
279     public static class ExitSessionDialogClickListener implements DialogInterface.OnClickListener {
280         private final UserSwitcherController mUserSwitcherController;
281         private final boolean mIsEphemeral;
282         private final int mUserId;
283         private final DialogInterface mDialog;
284 
285         @AssistedFactory
286         public interface Factory {
create( boolean isEphemeral, int userId, DialogInterface dialog)287             ExitSessionDialogClickListener create(
288                     boolean isEphemeral,
289                     int userId,
290                     DialogInterface dialog);
291         }
292 
293         @AssistedInject
ExitSessionDialogClickListener( UserSwitcherController userSwitcherController, @Assisted boolean isEphemeral, @Assisted int userId, @Assisted DialogInterface dialog )294         public ExitSessionDialogClickListener(
295                 UserSwitcherController userSwitcherController,
296                 @Assisted boolean isEphemeral,
297                 @Assisted int userId,
298                 @Assisted DialogInterface dialog
299         ) {
300             mUserSwitcherController = userSwitcherController;
301             mIsEphemeral = isEphemeral;
302             mUserId = userId;
303             mDialog = dialog;
304         }
305 
306         @Override
onClick(DialogInterface dialog, int which)307         public void onClick(DialogInterface dialog, int which) {
308             if (mIsEphemeral) {
309                 if (which == DialogInterface.BUTTON_POSITIVE) {
310                     // Ephemeral guest: exit guest, guest is removed by the system
311                     // on exit, since its marked ephemeral
312                     mUserSwitcherController.exitGuestUser(mUserId, UserHandle.USER_NULL, false);
313                 } else if (which == DialogInterface.BUTTON_NEUTRAL) {
314                     // Cancel clicked, do nothing
315                     mDialog.cancel();
316                 }
317             } else {
318                 if (which == DialogInterface.BUTTON_POSITIVE) {
319                     // Non-ephemeral guest: exit guest, guest is not removed by the system
320                     // on exit, since its marked non-ephemeral
321                     mUserSwitcherController.exitGuestUser(mUserId, UserHandle.USER_NULL, false);
322                 } else if (which == DialogInterface.BUTTON_NEGATIVE) {
323                     // Non-ephemeral guest: remove guest and then exit
324                     mUserSwitcherController.exitGuestUser(mUserId, UserHandle.USER_NULL, true);
325                 } else if (which == DialogInterface.BUTTON_NEUTRAL) {
326                     // Cancel clicked, do nothing
327                     mDialog.cancel();
328                 }
329             }
330         }
331     }
332 }
333