• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2021 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.globalactions;
18 
19 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
20 import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
21 import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
22 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
23 import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL;
24 import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_GLOBAL_ACTIONS;
25 import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_2BUTTON;
26 
27 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST;
28 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED;
29 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN;
30 
31 import android.animation.Animator;
32 import android.animation.AnimatorListenerAdapter;
33 import android.animation.ValueAnimator;
34 import android.annotation.Nullable;
35 import android.app.ActivityManager;
36 import android.app.Dialog;
37 import android.app.IActivityManager;
38 import android.app.StatusBarManager;
39 import android.app.WallpaperManager;
40 import android.app.trust.TrustManager;
41 import android.content.BroadcastReceiver;
42 import android.content.Context;
43 import android.content.DialogInterface;
44 import android.content.Intent;
45 import android.content.IntentFilter;
46 import android.content.pm.PackageManager;
47 import android.content.pm.UserInfo;
48 import android.content.res.ColorStateList;
49 import android.content.res.Configuration;
50 import android.content.res.Resources;
51 import android.database.ContentObserver;
52 import android.graphics.Color;
53 import android.graphics.drawable.Drawable;
54 import android.media.AudioManager;
55 import android.os.Binder;
56 import android.os.Build;
57 import android.os.Bundle;
58 import android.os.Handler;
59 import android.os.IBinder;
60 import android.os.Message;
61 import android.os.PowerManager;
62 import android.os.RemoteException;
63 import android.os.SystemClock;
64 import android.os.SystemProperties;
65 import android.os.Trace;
66 import android.os.UserHandle;
67 import android.os.UserManager;
68 import android.provider.Settings;
69 import android.sysprop.TelephonyProperties;
70 import android.telecom.TelecomManager;
71 import android.telephony.ServiceState;
72 import android.telephony.TelephonyCallback;
73 import android.telephony.TelephonyManager;
74 import android.util.ArraySet;
75 import android.util.Log;
76 import android.view.ContextThemeWrapper;
77 import android.view.GestureDetector;
78 import android.view.IWindowManager;
79 import android.view.LayoutInflater;
80 import android.view.MotionEvent;
81 import android.view.Surface;
82 import android.view.View;
83 import android.view.ViewGroup;
84 import android.view.Window;
85 import android.view.WindowManager;
86 import android.view.accessibility.AccessibilityEvent;
87 import android.view.accessibility.AccessibilityManager;
88 import android.widget.BaseAdapter;
89 import android.widget.ImageView;
90 import android.widget.ImageView.ScaleType;
91 import android.widget.LinearLayout;
92 import android.widget.ListPopupWindow;
93 import android.widget.TextView;
94 import android.window.OnBackInvokedCallback;
95 import android.window.OnBackInvokedDispatcher;
96 
97 import androidx.annotation.NonNull;
98 import androidx.lifecycle.Lifecycle;
99 import androidx.lifecycle.LifecycleOwner;
100 import androidx.lifecycle.LifecycleRegistry;
101 
102 import com.android.app.animation.Interpolators;
103 import com.android.internal.R;
104 import com.android.internal.annotations.VisibleForTesting;
105 import com.android.internal.colorextraction.ColorExtractor;
106 import com.android.internal.colorextraction.ColorExtractor.GradientColors;
107 import com.android.internal.jank.InteractionJankMonitor;
108 import com.android.internal.logging.MetricsLogger;
109 import com.android.internal.logging.UiEvent;
110 import com.android.internal.logging.UiEventLogger;
111 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
112 import com.android.internal.statusbar.IStatusBarService;
113 import com.android.internal.util.EmergencyAffordanceManager;
114 import com.android.internal.util.ScreenshotHelper;
115 import com.android.internal.widget.LockPatternUtils;
116 import com.android.keyguard.KeyguardUpdateMonitor;
117 import com.android.systemui.MultiListLayout;
118 import com.android.systemui.MultiListLayout.MultiListAdapter;
119 import com.android.systemui.animation.DialogCuj;
120 import com.android.systemui.animation.DialogTransitionAnimator;
121 import com.android.systemui.animation.Expandable;
122 import com.android.systemui.broadcast.BroadcastDispatcher;
123 import com.android.systemui.colorextraction.SysuiColorExtractor;
124 import com.android.systemui.dagger.qualifiers.Background;
125 import com.android.systemui.dagger.qualifiers.Main;
126 import com.android.systemui.display.data.repository.DisplayWindowPropertiesRepository;
127 import com.android.systemui.display.shared.model.DisplayWindowProperties;
128 import com.android.systemui.globalactions.domain.interactor.GlobalActionsInteractor;
129 import com.android.systemui.plugins.ActivityStarter;
130 import com.android.systemui.plugins.GlobalActions.GlobalActionsManager;
131 import com.android.systemui.plugins.GlobalActionsPanelPlugin;
132 import com.android.systemui.scrim.ScrimDrawable;
133 import com.android.systemui.settings.UserTracker;
134 import com.android.systemui.shade.ShadeController;
135 import com.android.systemui.shade.shared.flag.ShadeWindowGoesAround;
136 import com.android.systemui.statusbar.NotificationShadeWindowController;
137 import com.android.systemui.statusbar.VibratorHelper;
138 import com.android.systemui.statusbar.phone.LightBarController;
139 import com.android.systemui.statusbar.phone.SystemUIDialog;
140 import com.android.systemui.statusbar.policy.ConfigurationController;
141 import com.android.systemui.statusbar.policy.KeyguardStateController;
142 import com.android.systemui.statusbar.window.StatusBarWindowController;
143 import com.android.systemui.statusbar.window.StatusBarWindowControllerStore;
144 import com.android.systemui.telephony.TelephonyListenerManager;
145 import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
146 import com.android.systemui.user.domain.interactor.UserLogoutInteractor;
147 import com.android.systemui.util.EmergencyDialerConstants;
148 import com.android.systemui.util.RingerModeTracker;
149 import com.android.systemui.util.settings.GlobalSettings;
150 import com.android.systemui.util.settings.SecureSettings;
151 
152 import dagger.Lazy;
153 
154 import java.util.ArrayList;
155 import java.util.List;
156 import java.util.concurrent.Executor;
157 
158 import javax.inject.Inject;
159 
160 /**
161  * Helper to show the global actions dialog.  Each item is an {@link Action} that may show depending
162  * on whether the keyguard is showing, and whether the device is provisioned.
163  */
164 public class GlobalActionsDialogLite implements DialogInterface.OnDismissListener,
165         DialogInterface.OnShowListener,
166         ConfigurationController.ConfigurationListener,
167         GlobalActionsPanelPlugin.Callbacks,
168         LifecycleOwner {
169 
170     public static final String SYSTEM_DIALOG_REASON_KEY = "reason";
171     public static final String SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS = "globalactions";
172     public static final String SYSTEM_DIALOG_REASON_DREAM = "dream";
173 
174     private static final boolean DEBUG = false;
175 
176     private static final String TAG = "GlobalActionsDialogLite";
177 
178     private static final String INTERACTION_JANK_TAG = "global_actions";
179 
180     private static final boolean SHOW_SILENT_TOGGLE = true;
181 
182     /* Valid settings for global actions keys.
183      * see config.xml config_globalActionList */
184     @VisibleForTesting
185     static final String GLOBAL_ACTION_KEY_POWER = "power";
186     private static final String GLOBAL_ACTION_KEY_AIRPLANE = "airplane";
187     static final String GLOBAL_ACTION_KEY_BUGREPORT = "bugreport";
188     private static final String GLOBAL_ACTION_KEY_SILENT = "silent";
189     private static final String GLOBAL_ACTION_KEY_USERS = "users";
190     private static final String GLOBAL_ACTION_KEY_SETTINGS = "settings";
191     static final String GLOBAL_ACTION_KEY_LOCKDOWN = "lockdown";
192     private static final String GLOBAL_ACTION_KEY_VOICEASSIST = "voiceassist";
193     private static final String GLOBAL_ACTION_KEY_ASSIST = "assist";
194     static final String GLOBAL_ACTION_KEY_RESTART = "restart";
195     private static final String GLOBAL_ACTION_KEY_LOGOUT = "logout";
196     static final String GLOBAL_ACTION_KEY_EMERGENCY = "emergency";
197     static final String GLOBAL_ACTION_KEY_SCREENSHOT = "screenshot";
198     static final String GLOBAL_ACTION_KEY_SYSTEM_UPDATE = "system_update";
199     static final String GLOBAL_ACTION_KEY_STANDBY = "standby";
200 
201     // See NotificationManagerService#scheduleDurationReachedLocked
202     private static final long TOAST_FADE_TIME = 333;
203     // See NotificationManagerService.LONG_DELAY
204     private static final int TOAST_VISIBLE_TIME = 3500;
205 
206     private static final int DIALOG_WINDOW_TYPE = TYPE_STATUS_BAR_SUB_PANEL;
207 
208     private final Context mContext;
209     private final GlobalActionsManager mWindowManagerFuncs;
210     private final AudioManager mAudioManager;
211     private final LockPatternUtils mLockPatternUtils;
212     private final SelectedUserInteractor mSelectedUserInteractor;
213     private final TelephonyListenerManager mTelephonyListenerManager;
214     private final KeyguardStateController mKeyguardStateController;
215     private final BroadcastDispatcher mBroadcastDispatcher;
216     protected final GlobalSettings mGlobalSettings;
217     protected final SecureSettings mSecureSettings;
218     protected final Resources mResources;
219     private final ConfigurationController mConfigurationController;
220     private final UserTracker mUserTracker;
221     private final UserManager mUserManager;
222     private final TrustManager mTrustManager;
223     private final IActivityManager mIActivityManager;
224     private final TelecomManager mTelecomManager;
225     private final MetricsLogger mMetricsLogger;
226     private final UiEventLogger mUiEventLogger;
227     private final ActivityStarter mActivityStarter;
228 
229     // Used for RingerModeTracker
230     private final LifecycleRegistry mLifecycle = new LifecycleRegistry(this);
231 
232     @VisibleForTesting
233     protected final ArrayList<Action> mItems = new ArrayList<>();
234     @VisibleForTesting
235     protected final ArrayList<Action> mOverflowItems = new ArrayList<>();
236     @VisibleForTesting
237     protected final ArrayList<Action> mPowerItems = new ArrayList<>();
238 
239     @VisibleForTesting
240     protected ActionsDialogLite mDialog;
241 
242     private Action mSilentModeAction;
243     private ToggleAction mAirplaneModeOn;
244 
245     protected MyAdapter mAdapter;
246     protected MyOverflowAdapter mOverflowAdapter;
247     protected MyPowerOptionsAdapter mPowerAdapter;
248 
249     private boolean mKeyguardShowing = false;
250     private boolean mDeviceProvisioned = false;
251     private ToggleState mAirplaneState = ToggleState.Off;
252     private boolean mIsWaitingForEcmExit = false;
253     private boolean mHasTelephony;
254     private boolean mHasVibrator;
255     private final boolean mShowSilentToggle;
256     private final boolean mIsTv;
257     private final EmergencyAffordanceManager mEmergencyAffordanceManager;
258     private final ScreenshotHelper mScreenshotHelper;
259     private final SysuiColorExtractor mSysuiColorExtractor;
260     private final IStatusBarService mStatusBarService;
261     protected final LightBarController mLightBarController;
262     protected final NotificationShadeWindowController mNotificationShadeWindowController;
263     private final StatusBarWindowControllerStore mStatusBarWindowControllerStore;
264     private final IWindowManager mIWindowManager;
265     private final Executor mBackgroundExecutor;
266     private final RingerModeTracker mRingerModeTracker;
267     private int mDialogPressDelay = DIALOG_PRESS_DELAY; // ms
268     protected Handler mMainHandler;
269     private int mSmallestScreenWidthDp;
270     private int mOrientation;
271     private final ShadeController mShadeController;
272     private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
273     private final DialogTransitionAnimator mDialogTransitionAnimator;
274     private final UserLogoutInteractor mLogoutInteractor;
275     private final GlobalActionsInteractor mInteractor;
276     private final Lazy<DisplayWindowPropertiesRepository> mDisplayWindowPropertiesRepositoryLazy;
277     private final PowerManager mPowerManager;
278     private final Handler mHandler;
279 
280     private final UserTracker.Callback mOnUserSwitched = new UserTracker.Callback() {
281         @Override
282         public void onBeforeUserSwitching(int newUser) {
283             // Dismiss the dialog as soon as we start switching. This will schedule a message
284             // in a handler so it will be pretty quick.
285             dismissDialog();
286         }
287     };
288 
289     @VisibleForTesting
290     public enum GlobalActionsEvent implements UiEventLogger.UiEventEnum {
291         @UiEvent(doc = "The global actions / power menu surface became visible on the screen.")
292         GA_POWER_MENU_OPEN(337),
293 
294         @UiEvent(doc = "The global actions / power menu surface was dismissed.")
295         GA_POWER_MENU_CLOSE(471),
296 
297         @UiEvent(doc = "The global actions bugreport button was pressed.")
298         GA_BUGREPORT_PRESS(344),
299 
300         @UiEvent(doc = "The global actions bugreport button was long pressed.")
301         GA_BUGREPORT_LONG_PRESS(345),
302 
303         @UiEvent(doc = "The global actions emergency button was pressed.")
304         GA_EMERGENCY_DIALER_PRESS(346),
305 
306         @UiEvent(doc = "The global actions screenshot button was pressed.")
307         GA_SCREENSHOT_PRESS(347),
308 
309         @UiEvent(doc = "The global actions screenshot button was long pressed.")
310         GA_SCREENSHOT_LONG_PRESS(348),
311 
312         @UiEvent(doc = "The global actions power off button was pressed.")
313         GA_SHUTDOWN_PRESS(802),
314 
315         @UiEvent(doc = "The global actions power off button was long pressed.")
316         GA_SHUTDOWN_LONG_PRESS(803),
317 
318         @UiEvent(doc = "The global actions reboot button was pressed.")
319         GA_REBOOT_PRESS(349),
320 
321         @UiEvent(doc = "The global actions reboot button was long pressed.")
322         GA_REBOOT_LONG_PRESS(804),
323 
324         @UiEvent(doc = "The global actions lockdown button was pressed.")
325         GA_LOCKDOWN_PRESS(354), // already created by cwren apparently
326 
327         @UiEvent(doc = "Power menu was opened via quick settings button.")
328         GA_OPEN_QS(805),
329 
330         @UiEvent(doc = "Power menu was opened via power + volume up.")
331         GA_OPEN_POWER_VOLUP(806),
332 
333         @UiEvent(doc = "Power menu was opened via long press on power.")
334         GA_OPEN_LONG_PRESS_POWER(807),
335 
336         @UiEvent(doc = "Power menu was closed via long press on power.")
337         GA_CLOSE_LONG_PRESS_POWER(808),
338 
339         @UiEvent(doc = "Power menu was dismissed by back gesture.")
340         GA_CLOSE_BACK(809),
341 
342         @UiEvent(doc = "Power menu was dismissed by tapping outside dialog.")
343         GA_CLOSE_TAP_OUTSIDE(810),
344 
345         @UiEvent(doc = "Power menu was closed via power + volume up.")
346         GA_CLOSE_POWER_VOLUP(811),
347 
348         @UiEvent(doc = "System Update button was pressed.")
349         GA_SYSTEM_UPDATE_PRESS(1716),
350 
351         @UiEvent(doc = "The global actions standby button was pressed.")
352         GA_STANDBY_PRESS(2210);
353 
354         private final int mId;
355 
GlobalActionsEvent(int id)356         GlobalActionsEvent(int id) {
357             mId = id;
358         }
359 
360         @Override
getId()361         public int getId() {
362             return mId;
363         }
364     }
365 
366     /**
367      * @param context everything needs a context :(
368      */
369     @Inject
GlobalActionsDialogLite( Context context, GlobalActionsManager windowManagerFuncs, AudioManager audioManager, LockPatternUtils lockPatternUtils, BroadcastDispatcher broadcastDispatcher, TelephonyListenerManager telephonyListenerManager, GlobalSettings globalSettings, SecureSettings secureSettings, @NonNull VibratorHelper vibrator, @Main Resources resources, ConfigurationController configurationController, ActivityStarter activityStarter, UserTracker userTracker, KeyguardStateController keyguardStateController, UserManager userManager, TrustManager trustManager, IActivityManager iActivityManager, @Nullable TelecomManager telecomManager, MetricsLogger metricsLogger, SysuiColorExtractor colorExtractor, IStatusBarService statusBarService, LightBarController lightBarController, NotificationShadeWindowController notificationShadeWindowController, StatusBarWindowControllerStore statusBarWindowControllerStore, IWindowManager iWindowManager, @Background Executor backgroundExecutor, UiEventLogger uiEventLogger, RingerModeTracker ringerModeTracker, @Main Handler handler, PackageManager packageManager, ShadeController shadeController, KeyguardUpdateMonitor keyguardUpdateMonitor, DialogTransitionAnimator dialogTransitionAnimator, SelectedUserInteractor selectedUserInteractor, UserLogoutInteractor logoutInteractor, GlobalActionsInteractor interactor, Lazy<DisplayWindowPropertiesRepository> displayWindowPropertiesRepository, PowerManager powerManager)370     public GlobalActionsDialogLite(
371             Context context,
372             GlobalActionsManager windowManagerFuncs,
373             AudioManager audioManager,
374             LockPatternUtils lockPatternUtils,
375             BroadcastDispatcher broadcastDispatcher,
376             TelephonyListenerManager telephonyListenerManager,
377             GlobalSettings globalSettings,
378             SecureSettings secureSettings,
379             @NonNull VibratorHelper vibrator,
380             @Main Resources resources,
381             ConfigurationController configurationController,
382             ActivityStarter activityStarter,
383             UserTracker userTracker,
384             KeyguardStateController keyguardStateController,
385             UserManager userManager,
386             TrustManager trustManager,
387             IActivityManager iActivityManager,
388             @Nullable TelecomManager telecomManager,
389             MetricsLogger metricsLogger,
390             SysuiColorExtractor colorExtractor,
391             IStatusBarService statusBarService,
392             LightBarController lightBarController,
393             NotificationShadeWindowController notificationShadeWindowController,
394             StatusBarWindowControllerStore statusBarWindowControllerStore,
395             IWindowManager iWindowManager,
396             @Background Executor backgroundExecutor,
397             UiEventLogger uiEventLogger,
398             RingerModeTracker ringerModeTracker,
399             @Main Handler handler,
400             PackageManager packageManager,
401             ShadeController shadeController,
402             KeyguardUpdateMonitor keyguardUpdateMonitor,
403             DialogTransitionAnimator dialogTransitionAnimator,
404             SelectedUserInteractor selectedUserInteractor,
405             UserLogoutInteractor logoutInteractor,
406             GlobalActionsInteractor interactor,
407             Lazy<DisplayWindowPropertiesRepository> displayWindowPropertiesRepository,
408             PowerManager powerManager) {
409         mContext = context;
410         mWindowManagerFuncs = windowManagerFuncs;
411         mAudioManager = audioManager;
412         mLockPatternUtils = lockPatternUtils;
413         mTelephonyListenerManager = telephonyListenerManager;
414         mKeyguardStateController = keyguardStateController;
415         mBroadcastDispatcher = broadcastDispatcher;
416         mGlobalSettings = globalSettings;
417         mSecureSettings = secureSettings;
418         mResources = resources;
419         mConfigurationController = configurationController;
420         mActivityStarter = activityStarter;
421         mUserTracker = userTracker;
422         mUserManager = userManager;
423         mTrustManager = trustManager;
424         mIActivityManager = iActivityManager;
425         mTelecomManager = telecomManager;
426         mMetricsLogger = metricsLogger;
427         mUiEventLogger = uiEventLogger;
428         mSysuiColorExtractor = colorExtractor;
429         mStatusBarService = statusBarService;
430         mLightBarController = lightBarController;
431         mNotificationShadeWindowController = notificationShadeWindowController;
432         mStatusBarWindowControllerStore = statusBarWindowControllerStore;
433         mIWindowManager = iWindowManager;
434         mBackgroundExecutor = backgroundExecutor;
435         mRingerModeTracker = ringerModeTracker;
436         mMainHandler = handler;
437         mSmallestScreenWidthDp = resources.getConfiguration().smallestScreenWidthDp;
438         mOrientation = resources.getConfiguration().orientation;
439         mShadeController = shadeController;
440         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
441         mDialogTransitionAnimator = dialogTransitionAnimator;
442         mSelectedUserInteractor = selectedUserInteractor;
443         mLogoutInteractor = logoutInteractor;
444         mInteractor = interactor;
445         mDisplayWindowPropertiesRepositoryLazy = displayWindowPropertiesRepository;
446         mPowerManager = powerManager;
447 
448         mHandler = new Handler(mMainHandler.getLooper()) {
449             public void handleMessage(Message msg) {
450                 switch (msg.what) {
451                     case MESSAGE_DISMISS:
452                         if (mDialog != null) {
453                             if (SYSTEM_DIALOG_REASON_DREAM.equals(msg.obj)) {
454                                 // Hide instantly.
455                                 mDialog.hide();
456                                 mDialog.dismiss();
457                             } else {
458                                 mDialog.dismiss();
459                             }
460                             mDialog = null;
461                         }
462                         break;
463                     case MESSAGE_REFRESH:
464                         refreshSilentMode();
465                         mAdapter.notifyDataSetChanged();
466                         break;
467                 }
468             }
469         };
470 
471         // receive broadcasts
472         IntentFilter filter = new IntentFilter();
473         filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
474         filter.addAction(Intent.ACTION_SCREEN_OFF);
475         filter.addAction(TelephonyManager.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED);
476         mBroadcastDispatcher.registerReceiver(mBroadcastReceiver, filter);
477 
478         mHasTelephony = packageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY);
479         mIsTv = packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK);
480 
481         // get notified of phone state changes
482         mTelephonyListenerManager.addServiceStateListener(mPhoneStateListener);
483         mGlobalSettings.registerContentObserverSync(
484                 Settings.Global.getUriFor(Settings.Global.AIRPLANE_MODE_ON), true,
485                 mAirplaneModeObserver);
486         mHasVibrator = vibrator.hasVibrator();
487 
488         mShowSilentToggle = SHOW_SILENT_TOGGLE && !resources.getBoolean(
489                 R.bool.config_useFixedVolume);
490         if (mShowSilentToggle) {
491             mRingerModeTracker.getRingerMode().observe(this, ringer ->
492                     mHandler.sendEmptyMessage(MESSAGE_REFRESH)
493             );
494         }
495 
496         mEmergencyAffordanceManager = new EmergencyAffordanceManager(context);
497         mScreenshotHelper = new ScreenshotHelper(context);
498 
499         mConfigurationController.addCallback(this);
500     }
501 
502     /**
503      * Clean up callbacks
504      */
destroy()505     public void destroy() {
506         mBroadcastDispatcher.unregisterReceiver(mBroadcastReceiver);
507         mTelephonyListenerManager.removeServiceStateListener(mPhoneStateListener);
508         mGlobalSettings.unregisterContentObserverSync(mAirplaneModeObserver);
509         mConfigurationController.removeCallback(this);
510         if (mShowSilentToggle) {
511             mRingerModeTracker.getRingerMode().removeObservers(this);
512         }
513     }
514 
getContext()515     protected Context getContext() {
516         return mContext;
517     }
518 
getEventLogger()519     protected UiEventLogger getEventLogger() {
520         return mUiEventLogger;
521     }
522 
getKeyguardUpdateMonitor()523     protected KeyguardUpdateMonitor getKeyguardUpdateMonitor() {
524         return mKeyguardUpdateMonitor;
525     }
526 
527     /**
528      * Show the global actions dialog (creating if necessary) or hide it if it's already showing.
529      *
530      * @param keyguardShowing     True if keyguard is showing
531      * @param isDeviceProvisioned True if device is provisioned
532      * @param expandable          The expandable from which we should animate the dialog when
533      *                            showing it
534      * @param displayId           Display that should show the dialog
535      */
showOrHideDialog(boolean keyguardShowing, boolean isDeviceProvisioned, @Nullable Expandable expandable, int displayId)536     public void showOrHideDialog(boolean keyguardShowing, boolean isDeviceProvisioned,
537             @Nullable Expandable expandable, int displayId) {
538         mKeyguardShowing = keyguardShowing;
539         mDeviceProvisioned = isDeviceProvisioned;
540         if (mDialog != null && mDialog.isShowing()) {
541             // In order to force global actions to hide on the same affordance press, we must
542             // register a call to onGlobalActionsShown() first to prevent the default actions
543             // menu from showing. This will be followed by a subsequent call to
544             // onGlobalActionsHidden() on dismiss()
545             mWindowManagerFuncs.onGlobalActionsShown();
546             mDialog.dismiss();
547             mDialog = null;
548         } else {
549             handleShow(expandable, displayId);
550         }
551     }
552 
isKeyguardShowing()553     protected boolean isKeyguardShowing() {
554         return mKeyguardShowing;
555     }
556 
isDeviceProvisioned()557     protected boolean isDeviceProvisioned() {
558         return mDeviceProvisioned;
559     }
560 
561     /**
562      * Dismiss the global actions dialog, if it's currently shown
563      */
dismissDialog()564     public void dismissDialog() {
565         mHandler.removeMessages(MESSAGE_DISMISS);
566         mHandler.sendEmptyMessage(MESSAGE_DISMISS);
567     }
568 
handleShow(@ullable Expandable expandable, int displayId)569     protected void handleShow(@Nullable Expandable expandable, int displayId) {
570         mDialog = createDialog(displayId);
571         prepareDialog();
572 
573         WindowManager.LayoutParams attrs = mDialog.getWindow().getAttributes();
574         attrs.setTitle("ActionsDialog");
575         attrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
576         mDialog.getWindow().setAttributes(attrs);
577         // Don't acquire soft keyboard focus, to avoid destroying state when capturing bugreports
578         mDialog.getWindow().addFlags(FLAG_ALT_FOCUSABLE_IM);
579 
580         DialogTransitionAnimator.Controller controller =
581                 expandable != null ? expandable.dialogTransitionController(
582                         new DialogCuj(InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN,
583                                 INTERACTION_JANK_TAG)) : null;
584         mUserTracker.addCallback(mOnUserSwitched, mBackgroundExecutor);
585         if (controller != null) {
586             mDialogTransitionAnimator.show(mDialog, controller);
587         } else {
588             mDialog.show();
589         }
590         mWindowManagerFuncs.onGlobalActionsShown();
591     }
592 
593     @VisibleForTesting
shouldShowAction(Action action)594     protected boolean shouldShowAction(Action action) {
595         if (mKeyguardShowing && !action.showDuringKeyguard()) {
596             return false;
597         }
598         if (!mDeviceProvisioned && !action.showBeforeProvisioning()) {
599             return false;
600         }
601         return action.shouldShow();
602     }
603 
604     /**
605      * Returns the maximum number of power menu items to show based on which GlobalActions
606      * layout is being used.
607      */
608     @VisibleForTesting
getMaxShownPowerItems()609     protected int getMaxShownPowerItems() {
610         return mResources.getInteger(com.android.systemui.res.R.integer.power_menu_lite_max_columns)
611                 * mResources.getInteger(com.android.systemui.res.R.integer.power_menu_lite_max_rows);
612     }
613 
614     /**
615      * Add a power menu action item for to either the main or overflow items lists, depending on
616      * whether controls are enabled and whether the max number of shown items has been reached.
617      */
addActionItem(Action action)618     private void addActionItem(Action action) {
619         if (mItems.size() < getMaxShownPowerItems()) {
620             mItems.add(action);
621         } else {
622             mOverflowItems.add(action);
623         }
624     }
625 
626     @VisibleForTesting
getDefaultActions()627     protected String[] getDefaultActions() {
628         return mResources.getStringArray(R.array.config_globalActionsList);
629     }
630 
addIfShouldShowAction(List<Action> actions, Action action)631     private void addIfShouldShowAction(List<Action> actions, Action action) {
632         if (shouldShowAction(action)) {
633             actions.add(action);
634         }
635     }
636 
637     @VisibleForTesting
createActionItems()638     protected void createActionItems() {
639         // Simple toggle style if there's no vibrator, otherwise use a tri-state
640         if (!mHasVibrator) {
641             mSilentModeAction = new SilentModeToggleAction();
642         } else {
643             mSilentModeAction = new SilentModeTriStateAction(mAudioManager, mHandler);
644         }
645         mAirplaneModeOn = new AirplaneModeAction();
646         onAirplaneModeChanged();
647 
648         mItems.clear();
649         mOverflowItems.clear();
650         mPowerItems.clear();
651         String[] defaultActions = getDefaultActions();
652 
653         ShutDownAction shutdownAction = new ShutDownAction();
654         RestartAction restartAction = new RestartAction();
655         ArraySet<String> addedKeys = new ArraySet<>();
656         List<Action> tempActions = new ArrayList<>();
657         CurrentUserProvider currentUser = new CurrentUserProvider();
658 
659         // make sure emergency affordance action is first, if needed
660         if (mEmergencyAffordanceManager.needsEmergencyAffordance()) {
661             addIfShouldShowAction(tempActions, new EmergencyAffordanceAction());
662             addedKeys.add(GLOBAL_ACTION_KEY_EMERGENCY);
663         }
664 
665         for (int i = 0; i < defaultActions.length; i++) {
666             String actionKey = defaultActions[i];
667             if (addedKeys.contains(actionKey)) {
668                 // If we already have added this, don't add it again.
669                 continue;
670             }
671             if (GLOBAL_ACTION_KEY_POWER.equals(actionKey)) {
672                 addIfShouldShowAction(tempActions, shutdownAction);
673             } else if (GLOBAL_ACTION_KEY_AIRPLANE.equals(actionKey)) {
674                 addIfShouldShowAction(tempActions, mAirplaneModeOn);
675             } else if (GLOBAL_ACTION_KEY_BUGREPORT.equals(actionKey)) {
676                 if (shouldDisplayBugReport(currentUser.get())) {
677                     addIfShouldShowAction(tempActions, new BugReportAction());
678                 }
679             } else if (GLOBAL_ACTION_KEY_SILENT.equals(actionKey)) {
680                 if (mShowSilentToggle) {
681                     addIfShouldShowAction(tempActions, mSilentModeAction);
682                 }
683             } else if (GLOBAL_ACTION_KEY_USERS.equals(actionKey)) {
684                 if (SystemProperties.getBoolean("fw.power_user_switcher", false)) {
685                     addUserActions(tempActions, currentUser.get());
686                 }
687             } else if (GLOBAL_ACTION_KEY_SETTINGS.equals(actionKey)) {
688                 addIfShouldShowAction(tempActions, getSettingsAction());
689             } else if (GLOBAL_ACTION_KEY_LOCKDOWN.equals(actionKey)) {
690                 if (shouldDisplayLockdown(currentUser.get())) {
691                     addIfShouldShowAction(tempActions, new LockDownAction());
692                 }
693             } else if (GLOBAL_ACTION_KEY_VOICEASSIST.equals(actionKey)) {
694                 addIfShouldShowAction(tempActions, getVoiceAssistAction());
695             } else if (GLOBAL_ACTION_KEY_ASSIST.equals(actionKey)) {
696                 addIfShouldShowAction(tempActions, getAssistAction());
697             } else if (GLOBAL_ACTION_KEY_RESTART.equals(actionKey)) {
698                 addIfShouldShowAction(tempActions, restartAction);
699             } else if (GLOBAL_ACTION_KEY_SCREENSHOT.equals(actionKey)) {
700                 addIfShouldShowAction(tempActions, new ScreenshotAction());
701             } else if (GLOBAL_ACTION_KEY_LOGOUT.equals(actionKey)) {
702                 if (mLogoutInteractor.isLogoutEnabled().getValue()) {
703                     addIfShouldShowAction(tempActions, new LogoutAction());
704                 }
705             } else if (GLOBAL_ACTION_KEY_EMERGENCY.equals(actionKey)) {
706                 if (shouldDisplayEmergency()) {
707                     addIfShouldShowAction(tempActions, new EmergencyDialerAction());
708                 }
709             } else if (GLOBAL_ACTION_KEY_SYSTEM_UPDATE.equals(actionKey)) {
710                 addIfShouldShowAction(tempActions, new SystemUpdateAction());
711             } else if (GLOBAL_ACTION_KEY_STANDBY.equals(actionKey)) {
712                 addIfShouldShowAction(tempActions, new StandbyAction());
713             } else {
714                 Log.e(TAG, "Invalid global action key " + actionKey);
715             }
716             // Add here so we don't add more than one.
717             addedKeys.add(actionKey);
718         }
719 
720         // replace power and restart with a single power options action, if needed
721         if (tempActions.contains(shutdownAction) && tempActions.contains(restartAction)
722                 && tempActions.size() > getMaxShownPowerItems()) {
723             // transfer shutdown and restart to their own list of power actions
724             int powerOptionsIndex = Math.min(tempActions.indexOf(restartAction),
725                     tempActions.indexOf(shutdownAction));
726             tempActions.remove(shutdownAction);
727             tempActions.remove(restartAction);
728             mPowerItems.add(shutdownAction);
729             mPowerItems.add(restartAction);
730 
731             // add the PowerOptionsAction after Emergency, if present
732             tempActions.add(powerOptionsIndex, new PowerOptionsAction());
733         }
734         for (Action action : tempActions) {
735             addActionItem(action);
736         }
737     }
738 
onRefresh()739     protected void onRefresh() {
740         // re-allocate actions between main and overflow lists
741         this.createActionItems();
742     }
743 
initDialogItems()744     protected void initDialogItems() {
745         createActionItems();
746         mAdapter = new MyAdapter();
747         mOverflowAdapter = new MyOverflowAdapter();
748         mPowerAdapter = new MyPowerOptionsAdapter();
749     }
750 
751 
752     /**
753      * Create the global actions dialog.
754      *
755      * @return A new dialog.
756      */
createDialog()757     protected ActionsDialogLite createDialog() {
758         return createDialog(mContext.getDisplayId());
759     }
760 
getContextForDisplay(int displayId)761     private Context getContextForDisplay(int displayId) {
762         if (!ShadeWindowGoesAround.isEnabled()) {
763             Log.e(TAG, "Asked for the displayId=" + displayId
764                     + " context but returning default display one as ShadeWindowGoesAround flag "
765                     + "is disabled.");
766             return mContext;
767         }
768         try {
769             DisplayWindowProperties properties = mDisplayWindowPropertiesRepositoryLazy.get().get(
770                     displayId,
771                     DIALOG_WINDOW_TYPE);
772             return properties.getContext();
773         } catch (Exception e) {
774             Log.e(TAG, "Couldn't get context for displayId=" + displayId);
775             return mContext;
776         }
777     }
778     /**
779      * Create the global actions dialog with a specific context.
780      *
781      * @return A new dialog.
782      */
createDialog(int displayId)783     protected ActionsDialogLite createDialog(int displayId) {
784         final Context context = getContextForDisplay(displayId);
785         initDialogItems();
786 
787         ActionsDialogLite dialog = new ActionsDialogLite(
788                 context,
789                 com.android.systemui.res.R.style.Theme_SystemUI_Dialog_GlobalActionsLite,
790                 mAdapter,
791                 mOverflowAdapter,
792                 mSysuiColorExtractor,
793                 mStatusBarService,
794                 mLightBarController,
795                 mKeyguardStateController,
796                 mNotificationShadeWindowController,
797                 mStatusBarWindowControllerStore.forDisplay(context.getDisplayId()),
798                 this::onRefresh,
799                 mKeyguardShowing,
800                 mPowerAdapter,
801                 mUiEventLogger,
802                 mShadeController,
803                 mKeyguardUpdateMonitor,
804                 mLockPatternUtils,
805                 mSelectedUserInteractor);
806 
807         dialog.setOnDismissListener(this);
808         dialog.setOnShowListener(this);
809 
810         return dialog;
811     }
812 
813     @VisibleForTesting
shouldDisplayLockdown(UserInfo user)814     boolean shouldDisplayLockdown(UserInfo user) {
815         if (user == null) {
816             return false;
817         }
818 
819         int userId = user.id;
820 
821         // Lockdown is meaningless without a place to go.
822         if (!mKeyguardStateController.isMethodSecure()) {
823             return false;
824         }
825 
826         // Only show the lockdown button if the device isn't locked down (for whatever reason).
827         int state = mLockPatternUtils.getStrongAuthForUser(userId);
828         return (state == STRONG_AUTH_NOT_REQUIRED
829                 || state == SOME_AUTH_REQUIRED_AFTER_USER_REQUEST);
830     }
831 
832     @VisibleForTesting
shouldDisplayEmergency()833     boolean shouldDisplayEmergency() {
834         // Emergency calling requires a telephony radio.
835         return mHasTelephony;
836     }
837 
838     @VisibleForTesting
shouldDisplayBugReport(@ullable UserInfo user)839     boolean shouldDisplayBugReport(@Nullable UserInfo user) {
840         return user != null && user.isAdmin()
841                 && mSecureSettings.getIntForUser(Settings.Secure.BUGREPORT_IN_POWER_MENU, 0,
842                 user.id) != 0;
843     }
844 
845     @Override
onConfigChanged(Configuration newConfig)846     public void onConfigChanged(Configuration newConfig) {
847         if (mDialog != null && mDialog.isShowing()
848                 && (newConfig.smallestScreenWidthDp != mSmallestScreenWidthDp
849                 || newConfig.orientation != mOrientation)) {
850             mSmallestScreenWidthDp = newConfig.smallestScreenWidthDp;
851             mOrientation = newConfig.orientation;
852             mDialog.refreshDialog();
853         }
854     }
855 
856     /**
857      * Implements {@link GlobalActionsPanelPlugin.Callbacks#dismissGlobalActionsMenu()}, which is
858      * called when the quick access wallet requests dismissal.
859      */
860     @Override
dismissGlobalActionsMenu()861     public void dismissGlobalActionsMenu() {
862         dismissDialog();
863     }
864 
865     @VisibleForTesting
isTv()866     boolean isTv() {
867         return mIsTv;
868     }
869 
870     @VisibleForTesting
871     protected final class PowerOptionsAction extends SinglePressAction {
PowerOptionsAction()872         private PowerOptionsAction() {
873             super(com.android.systemui.res.R.drawable.ic_settings_power,
874                     R.string.global_action_power_options);
875         }
876 
877         @Override
showDuringKeyguard()878         public boolean showDuringKeyguard() {
879             return true;
880         }
881 
882         @Override
showBeforeProvisioning()883         public boolean showBeforeProvisioning() {
884             return true;
885         }
886 
887         @Override
onPress()888         public void onPress() {
889             if (mDialog != null) {
890                 mDialog.showPowerOptionsMenu();
891             }
892         }
893     }
894 
895     @VisibleForTesting
896     final class ShutDownAction extends SinglePressAction implements LongPressAction {
ShutDownAction()897         ShutDownAction() {
898             super(R.drawable.ic_lock_power_off,
899                     R.string.global_action_power_off);
900         }
901 
902         @Override
onLongPress()903         public boolean onLongPress() {
904             // don't actually trigger the reboot if we are running stability
905             // tests via monkey
906             if (ActivityManager.isUserAMonkey()) {
907                 return false;
908             }
909             mUiEventLogger.log(GlobalActionsEvent.GA_SHUTDOWN_LONG_PRESS);
910             if (!mUserManager.hasUserRestriction(UserManager.DISALLOW_SAFE_BOOT)) {
911                 mWindowManagerFuncs.reboot(true);
912                 return true;
913             }
914             return false;
915         }
916 
917         @Override
showDuringKeyguard()918         public boolean showDuringKeyguard() {
919             return true;
920         }
921 
922         @Override
showBeforeProvisioning()923         public boolean showBeforeProvisioning() {
924             return true;
925         }
926 
927         @Override
onPress()928         public void onPress() {
929             // don't actually trigger the shutdown if we are running stability
930             // tests via monkey
931             if (ActivityManager.isUserAMonkey()) {
932                 return;
933             }
934             mUiEventLogger.log(GlobalActionsEvent.GA_SHUTDOWN_PRESS);
935             // shutdown by making sure radio and power are handled accordingly.
936             mWindowManagerFuncs.shutdown();
937         }
938     }
939 
940     @VisibleForTesting
941     protected abstract class EmergencyAction extends SinglePressAction {
EmergencyAction(int iconResId, int messageResId)942         EmergencyAction(int iconResId, int messageResId) {
943             super(iconResId, messageResId);
944         }
945 
946         @Override
shouldBeSeparated()947         public boolean shouldBeSeparated() {
948             return false;
949         }
950 
951         @Override
create( Context context, View convertView, ViewGroup parent, LayoutInflater inflater)952         public View create(
953                 Context context, View convertView, ViewGroup parent, LayoutInflater inflater) {
954             View v = super.create(context, convertView, parent, inflater);
955             int textColor = getEmergencyTextColor(context);
956             int iconColor = getEmergencyIconColor(context);
957             int backgroundColor = getEmergencyBackgroundColor(context);
958             TextView messageView = v.findViewById(R.id.message);
959             messageView.setTextColor(textColor);
960             messageView.setSelected(true); // necessary for marquee to work
961             ImageView icon = v.findViewById(R.id.icon);
962             icon.getDrawable().setTint(iconColor);
963             icon.setBackgroundTintList(ColorStateList.valueOf(backgroundColor));
964             v.setBackgroundTintList(ColorStateList.valueOf(backgroundColor));
965             return v;
966         }
967 
968         @Override
showDuringKeyguard()969         public boolean showDuringKeyguard() {
970             return true;
971         }
972 
973         @Override
showBeforeProvisioning()974         public boolean showBeforeProvisioning() {
975             return true;
976         }
977     }
978 
getEmergencyTextColor(Context context)979     protected int getEmergencyTextColor(Context context) {
980         return context.getResources().getColor(
981                 com.android.systemui.res.R.color.global_actions_lite_text);
982     }
983 
getEmergencyIconColor(Context context)984     protected int getEmergencyIconColor(Context context) {
985         return context.getResources().getColor(
986                 com.android.systemui.res.R.color.global_actions_lite_emergency_icon);
987     }
988 
getEmergencyBackgroundColor(Context context)989     protected int getEmergencyBackgroundColor(Context context) {
990         return context.getResources().getColor(
991                 com.android.systemui.res.R.color.global_actions_lite_emergency_background);
992     }
993 
994     private class EmergencyAffordanceAction extends EmergencyAction {
EmergencyAffordanceAction()995         EmergencyAffordanceAction() {
996             super(R.drawable.emergency_icon,
997                     R.string.global_action_emergency);
998         }
999 
1000         @Override
onPress()1001         public void onPress() {
1002             mEmergencyAffordanceManager.performEmergencyCall();
1003         }
1004     }
1005 
1006     @VisibleForTesting
1007     class EmergencyDialerAction extends EmergencyAction {
EmergencyDialerAction()1008         private EmergencyDialerAction() {
1009             super(com.android.systemui.res.R.drawable.ic_emergency_star,
1010                     R.string.global_action_emergency);
1011         }
1012 
1013         @Override
onPress()1014         public void onPress() {
1015             mMetricsLogger.action(MetricsEvent.ACTION_EMERGENCY_DIALER_FROM_POWER_MENU);
1016             mUiEventLogger.log(GlobalActionsEvent.GA_EMERGENCY_DIALER_PRESS);
1017             if (mTelecomManager != null) {
1018                 // Close shade so user sees the activity
1019                 mShadeController.cancelExpansionAndCollapseShade();
1020                 Intent intent = mTelecomManager.createLaunchEmergencyDialerIntent(
1021                         null /* number */);
1022                 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
1023                         | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
1024                         | Intent.FLAG_ACTIVITY_CLEAR_TOP);
1025                 intent.putExtra(EmergencyDialerConstants.EXTRA_ENTRY_TYPE,
1026                         EmergencyDialerConstants.ENTRY_TYPE_POWER_MENU);
1027                 mContext.startActivityAsUser(intent, mUserTracker.getUserHandle());
1028             }
1029         }
1030     }
1031 
1032     @VisibleForTesting
makeEmergencyDialerActionForTesting()1033     EmergencyDialerAction makeEmergencyDialerActionForTesting() {
1034         return new EmergencyDialerAction();
1035     }
1036 
1037     @VisibleForTesting
1038     final class RestartAction extends SinglePressAction implements LongPressAction {
RestartAction()1039         RestartAction() {
1040             super(R.drawable.ic_restart, R.string.global_action_restart);
1041         }
1042 
1043         @Override
onLongPress()1044         public boolean onLongPress() {
1045             // don't actually trigger the reboot if we are running stability
1046             // tests via monkey
1047             if (ActivityManager.isUserAMonkey()) {
1048                 return false;
1049             }
1050             mUiEventLogger.log(GlobalActionsEvent.GA_REBOOT_LONG_PRESS);
1051             if (!mUserManager.hasUserRestriction(UserManager.DISALLOW_SAFE_BOOT)) {
1052                 mWindowManagerFuncs.reboot(true);
1053                 return true;
1054             }
1055             return false;
1056         }
1057 
1058         @Override
showDuringKeyguard()1059         public boolean showDuringKeyguard() {
1060             return true;
1061         }
1062 
1063         @Override
showBeforeProvisioning()1064         public boolean showBeforeProvisioning() {
1065             return true;
1066         }
1067 
1068         @Override
onPress()1069         public void onPress() {
1070             // don't actually trigger the reboot if we are running stability
1071             // tests via monkey
1072             if (ActivityManager.isUserAMonkey()) {
1073                 return;
1074             }
1075             mUiEventLogger.log(GlobalActionsEvent.GA_REBOOT_PRESS);
1076             mWindowManagerFuncs.reboot(false);
1077         }
1078     }
1079 
1080     @VisibleForTesting
1081     class ScreenshotAction extends SinglePressAction {
ScreenshotAction()1082         ScreenshotAction() {
1083             super(R.drawable.ic_screenshot, R.string.global_action_screenshot);
1084         }
1085 
1086         @Override
onPress()1087         public void onPress() {
1088             // Add a little delay before executing, to give the
1089             // dialog a chance to go away before it takes a
1090             // screenshot.
1091             // TODO: instead, omit global action dialog layer
1092             mHandler.postDelayed(new Runnable() {
1093                 @Override
1094                 public void run() {
1095                     mScreenshotHelper.takeScreenshot(SCREENSHOT_GLOBAL_ACTIONS, mHandler, null);
1096                     mMetricsLogger.action(MetricsEvent.ACTION_SCREENSHOT_POWER_MENU);
1097                     mUiEventLogger.log(GlobalActionsEvent.GA_SCREENSHOT_PRESS);
1098                 }
1099             }, mDialogPressDelay);
1100         }
1101 
1102         @Override
showDuringKeyguard()1103         public boolean showDuringKeyguard() {
1104             return true;
1105         }
1106 
1107         @Override
showBeforeProvisioning()1108         public boolean showBeforeProvisioning() {
1109             return false;
1110         }
1111 
1112         @Override
shouldShow()1113         public boolean shouldShow() {
1114             // Include screenshot in power menu for legacy nav because it is not accessible
1115             // through Recents in that mode
1116             return is2ButtonNavigationEnabled();
1117         }
1118 
is2ButtonNavigationEnabled()1119         boolean is2ButtonNavigationEnabled() {
1120             return NAV_BAR_MODE_2BUTTON == mContext.getResources().getInteger(
1121                     com.android.internal.R.integer.config_navBarInteractionMode);
1122         }
1123     }
1124 
1125     @VisibleForTesting
makeScreenshotActionForTesting()1126     ScreenshotAction makeScreenshotActionForTesting() {
1127         return new ScreenshotAction();
1128     }
1129 
1130     @VisibleForTesting
1131     class BugReportAction extends SinglePressAction implements LongPressAction {
1132 
BugReportAction()1133         BugReportAction() {
1134             super(R.drawable.ic_lock_bugreport, R.string.bugreport_title);
1135         }
1136 
1137         @Override
onPress()1138         public void onPress() {
1139             // don't actually trigger the bugreport if we are running stability
1140             // tests via monkey
1141             if (ActivityManager.isUserAMonkey()) {
1142                 return;
1143             }
1144             Trace.instantForTrack(Trace.TRACE_TAG_APP, "bugreport", "BugReportAction#onPress");
1145             Log.d(TAG, "BugReportAction#onPress");
1146             // Add a little delay before executing, to give the
1147             // dialog a chance to go away before it takes a
1148             // screenshot.
1149             mHandler.postDelayed(new Runnable() {
1150                 @Override
1151                 public void run() {
1152                     try {
1153                         // Take an "interactive" bugreport.
1154                         mMetricsLogger.action(
1155                                 MetricsEvent.ACTION_BUGREPORT_FROM_POWER_MENU_INTERACTIVE);
1156                         mUiEventLogger.log(GlobalActionsEvent.GA_BUGREPORT_PRESS);
1157                         if (!mIActivityManager.launchBugReportHandlerApp()) {
1158                             Log.w(TAG, "Bugreport handler could not be launched");
1159                             Trace.instantForTrack(Trace.TRACE_TAG_APP, "bugreport",
1160                                     "BugReportAction#requestingInteractiveBugReport");
1161                             Log.d(TAG, "BugReportAction#requestingInteractiveBugReport");
1162                             mIActivityManager.requestInteractiveBugReport();
1163                         }
1164                     } catch (RemoteException e) {
1165                     }
1166                 }
1167             }, mDialogPressDelay);
1168         }
1169 
1170         @Override
onLongPress()1171         public boolean onLongPress() {
1172             // don't actually trigger the bugreport if we are running stability
1173             // tests via monkey
1174             if (ActivityManager.isUserAMonkey()) {
1175                 return false;
1176             }
1177             try {
1178                 // Take a "full" bugreport.
1179                 mMetricsLogger.action(MetricsEvent.ACTION_BUGREPORT_FROM_POWER_MENU_FULL);
1180                 mUiEventLogger.log(GlobalActionsEvent.GA_BUGREPORT_LONG_PRESS);
1181                 Trace.instantForTrack(Trace.TRACE_TAG_APP, "bugreport",
1182                         "BugReportAction#requestingFullBugReport");
1183                 Log.d(TAG, "BugReportAction#requestingFullBugReport");
1184                 mIActivityManager.requestFullBugReport();
1185             } catch (RemoteException e) {
1186             }
1187             return false;
1188         }
1189 
showDuringKeyguard()1190         public boolean showDuringKeyguard() {
1191             return true;
1192         }
1193 
1194         @Override
showBeforeProvisioning()1195         public boolean showBeforeProvisioning() {
1196             return Build.isDebuggable() && mSecureSettings.getIntForUser(
1197                     Settings.Secure.BUGREPORT_IN_POWER_MENU, 0, getCurrentUser().id) != 0
1198                     && getCurrentUser().isAdmin();
1199         }
1200     }
1201 
1202     @VisibleForTesting
makeBugReportActionForTesting()1203     BugReportAction makeBugReportActionForTesting() {
1204         return new BugReportAction();
1205     }
1206 
1207     private final class LogoutAction extends SinglePressAction {
LogoutAction()1208         private LogoutAction() {
1209             super(R.drawable.ic_logout, R.string.global_action_logout);
1210         }
1211 
1212         @Override
showDuringKeyguard()1213         public boolean showDuringKeyguard() {
1214             return true;
1215         }
1216 
1217         @Override
showBeforeProvisioning()1218         public boolean showBeforeProvisioning() {
1219             return false;
1220         }
1221 
1222         @Override
onPress()1223         public void onPress() {
1224             // Add a little delay before executing, to give the dialog a chance to go away before
1225             // switching user
1226             mHandler.postDelayed(() -> {
1227                 mLogoutInteractor.logOut();
1228             }, mDialogPressDelay);
1229         }
1230     }
1231 
1232     @VisibleForTesting
1233     final class SystemUpdateAction extends SinglePressAction {
1234 
SystemUpdateAction()1235         SystemUpdateAction() {
1236             super(com.android.settingslib.R.drawable.ic_system_update,
1237                     com.android.settingslib.R.string.system_update_settings_list_item_title);
1238         }
1239 
1240         @Override
onPress()1241         public void onPress() {
1242             mUiEventLogger.log(GlobalActionsEvent.GA_SYSTEM_UPDATE_PRESS);
1243             launchSystemUpdate();
1244         }
1245 
1246         @Override
showDuringKeyguard()1247         public boolean showDuringKeyguard() {
1248             return true;
1249         }
1250 
1251         @Override
showBeforeProvisioning()1252         public boolean showBeforeProvisioning() {
1253             return false;
1254         }
1255 
launchSystemUpdate()1256         private void launchSystemUpdate() {
1257             Intent intent = new Intent(Settings.ACTION_SYSTEM_UPDATE_SETTINGS);
1258             intent.addFlags(
1259                     Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
1260             // postStartActivityDismissingKeyguard is used for showing keyguard
1261             // input/pin/password screen if lockscreen is secured, before sending the intent.
1262             mActivityStarter.postStartActivityDismissingKeyguard(intent, 0);
1263         }
1264     }
1265 
1266     @VisibleForTesting
1267     class StandbyAction extends SinglePressAction {
StandbyAction()1268         StandbyAction() {
1269             super(R.drawable.ic_standby, R.string.global_action_standby);
1270         }
1271 
1272         @Override
onPress()1273         public void onPress() {
1274             // Add a little delay before executing, to give the dialog a chance to go away before
1275             // going to sleep. Otherwise, we see screen flicker randomly.
1276             mHandler.postDelayed(() -> {
1277                 mUiEventLogger.log(GlobalActionsEvent.GA_STANDBY_PRESS);
1278                 mBackgroundExecutor.execute(() -> {
1279                     mPowerManager.goToSleep(SystemClock.uptimeMillis(),
1280                             PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON, 0);
1281                 });
1282             }, mDialogPressDelay);
1283         }
1284 
1285         @Override
showDuringKeyguard()1286         public boolean showDuringKeyguard() {
1287             return true;
1288         }
1289 
1290         @Override
showBeforeProvisioning()1291         public boolean showBeforeProvisioning() {
1292             return true;
1293         }
1294     }
1295 
getSettingsAction()1296     private Action getSettingsAction() {
1297         return new SinglePressAction(R.drawable.ic_settings,
1298                 R.string.global_action_settings) {
1299 
1300             @Override
1301             public void onPress() {
1302                 Intent intent = new Intent(Settings.ACTION_SETTINGS);
1303                 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
1304                 mContext.startActivity(intent);
1305             }
1306 
1307             @Override
1308             public boolean showDuringKeyguard() {
1309                 return true;
1310             }
1311 
1312             @Override
1313             public boolean showBeforeProvisioning() {
1314                 return true;
1315             }
1316         };
1317     }
1318 
1319     private Action getAssistAction() {
1320         return new SinglePressAction(R.drawable.ic_action_assist_focused,
1321                 R.string.global_action_assist) {
1322             @Override
1323             public void onPress() {
1324                 Intent intent = new Intent(Intent.ACTION_ASSIST);
1325                 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
1326                 mContext.startActivity(intent);
1327             }
1328 
1329             @Override
1330             public boolean showDuringKeyguard() {
1331                 return true;
1332             }
1333 
1334             @Override
1335             public boolean showBeforeProvisioning() {
1336                 return true;
1337             }
1338         };
1339     }
1340 
1341     private Action getVoiceAssistAction() {
1342         return new SinglePressAction(R.drawable.ic_voice_search,
1343                 R.string.global_action_voice_assist) {
1344             @Override
1345             public void onPress() {
1346                 Intent intent = new Intent(Intent.ACTION_VOICE_ASSIST);
1347                 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
1348                 mContext.startActivity(intent);
1349             }
1350 
1351             @Override
1352             public boolean showDuringKeyguard() {
1353                 return true;
1354             }
1355 
1356             @Override
1357             public boolean showBeforeProvisioning() {
1358                 return true;
1359             }
1360         };
1361     }
1362 
1363     @VisibleForTesting
1364     class LockDownAction extends SinglePressAction {
1365         LockDownAction() {
1366             super(R.drawable.ic_lock_lockdown, R.string.global_action_lockdown);
1367         }
1368 
1369         @Override
1370         public void onPress() {
1371             mLockPatternUtils.requireStrongAuth(STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN,
1372                     UserHandle.USER_ALL);
1373             mUiEventLogger.log(GlobalActionsEvent.GA_LOCKDOWN_PRESS);
1374             try {
1375                 mIWindowManager.lockNow(null);
1376                 // Lock profiles (if any) on the background thread.
1377                 mBackgroundExecutor.execute(() -> lockProfiles());
1378             } catch (RemoteException e) {
1379                 Log.e(TAG, "Error while trying to lock device.", e);
1380             }
1381         }
1382 
1383         @Override
1384         public boolean showDuringKeyguard() {
1385             return true;
1386         }
1387 
1388         @Override
1389         public boolean showBeforeProvisioning() {
1390             return false;
1391         }
1392     }
1393 
1394     private void lockProfiles() {
1395         final int currentUserId = getCurrentUser().id;
1396         final int[] profileIds = mUserManager.getEnabledProfileIds(currentUserId);
1397         for (final int id : profileIds) {
1398             if (id != currentUserId) {
1399                 mTrustManager.setDeviceLockedForUser(id, true);
1400             }
1401         }
1402     }
1403 
1404     protected UserInfo getCurrentUser() {
1405         return mUserTracker.getUserInfo();
1406     }
1407 
1408     /**
1409      * Non-thread-safe current user provider that caches the result - helpful when a method needs
1410      * to fetch it an indeterminate number of times.
1411      */
1412     private class CurrentUserProvider {
1413         private UserInfo mUserInfo = null;
1414         private boolean mFetched = false;
1415 
1416         @Nullable
1417         UserInfo get() {
1418             if (!mFetched) {
1419                 mFetched = true;
1420                 mUserInfo = getCurrentUser();
1421             }
1422             return mUserInfo;
1423         }
1424     }
1425 
1426     private void addUserActions(List<Action> actions, UserInfo currentUser) {
1427         if (mUserManager.isUserSwitcherEnabled()) {
1428             List<UserInfo> users = mUserManager.getUsers();
1429             for (final UserInfo user : users) {
1430                 if (user.supportsSwitchToByUser()) {
1431                     boolean isCurrentUser = currentUser == null
1432                             ? user.id == 0 : (currentUser.id == user.id);
1433                     Drawable icon = user.iconPath != null ? Drawable.createFromPath(user.iconPath)
1434                             : null;
1435                     SinglePressAction switchToUser = new SinglePressAction(
1436                             R.drawable.ic_menu_cc, icon,
1437                             (user.name != null ? user.name : "Primary")
1438                                     + (isCurrentUser ? " \u2714" : "")) {
1439                         public void onPress() {
1440                             try {
1441                                 mIActivityManager.switchUser(user.id);
1442                             } catch (RemoteException re) {
1443                                 Log.e(TAG, "Couldn't switch user " + re);
1444                             }
1445                         }
1446 
1447                         public boolean showDuringKeyguard() {
1448                             return true;
1449                         }
1450 
1451                         public boolean showBeforeProvisioning() {
1452                             return false;
1453                         }
1454                     };
1455                     addIfShouldShowAction(actions, switchToUser);
1456                 }
1457             }
1458         }
1459     }
1460 
1461     protected void prepareDialog() {
1462         refreshSilentMode();
1463         mAirplaneModeOn.updateState(mAirplaneState);
1464         mAdapter.notifyDataSetChanged();
1465         mLifecycle.setCurrentState(Lifecycle.State.RESUMED);
1466     }
1467 
1468     private void refreshSilentMode() {
1469         if (!mHasVibrator) {
1470             Integer value = mRingerModeTracker.getRingerMode().getValue();
1471             final boolean silentModeOn = value != null && value != AudioManager.RINGER_MODE_NORMAL;
1472             ((ToggleAction) mSilentModeAction).updateState(
1473                     silentModeOn ? ToggleState.On : ToggleState.Off);
1474         }
1475     }
1476 
1477     /**
1478      * {@inheritDoc}
1479      */
1480     @Override
1481     public void onDismiss(DialogInterface dialog) {
1482         if (mDialog == dialog) {
1483             mDialog = null;
1484         }
1485         mUiEventLogger.log(GlobalActionsEvent.GA_POWER_MENU_CLOSE);
1486         mWindowManagerFuncs.onGlobalActionsHidden();
1487         mLifecycle.setCurrentState(Lifecycle.State.CREATED);
1488         mInteractor.onDismissed();
1489         mUserTracker.removeCallback(mOnUserSwitched);
1490     }
1491 
1492     /**
1493      * {@inheritDoc}
1494      */
1495     @Override
1496     public void onShow(DialogInterface dialog) {
1497         mMetricsLogger.visible(MetricsEvent.POWER_MENU);
1498         mUiEventLogger.log(GlobalActionsEvent.GA_POWER_MENU_OPEN);
1499         mInteractor.onShown();
1500     }
1501 
1502     /**
1503      * The adapter used for power menu items shown in the global actions dialog.
1504      */
1505     public class MyAdapter extends MultiListAdapter {
1506         private int countItems(boolean separated) {
1507             int count = 0;
1508             for (int i = 0; i < mItems.size(); i++) {
1509                 final Action action = mItems.get(i);
1510 
1511                 if (action.shouldBeSeparated() == separated) {
1512                     count++;
1513                 }
1514             }
1515             return count;
1516         }
1517 
1518         @Override
1519         public int countSeparatedItems() {
1520             return countItems(true);
1521         }
1522 
1523         @Override
1524         public int countListItems() {
1525             return countItems(false);
1526         }
1527 
1528         @Override
1529         public int getCount() {
1530             return countSeparatedItems() + countListItems();
1531         }
1532 
1533         @Override
1534         public boolean isEnabled(int position) {
1535             return getItem(position).isEnabled();
1536         }
1537 
1538         @Override
1539         public boolean areAllItemsEnabled() {
1540             return false;
1541         }
1542 
1543         @Override
1544         public Action getItem(int position) {
1545             int filteredPos = 0;
1546             for (int i = 0; i < mItems.size(); i++) {
1547                 final Action action = mItems.get(i);
1548                 if (!shouldShowAction(action)) {
1549                     continue;
1550                 }
1551                 if (filteredPos == position) {
1552                     return action;
1553                 }
1554                 filteredPos++;
1555             }
1556 
1557             throw new IllegalArgumentException("position " + position
1558                     + " out of range of showable actions"
1559                     + ", filtered count=" + getCount()
1560                     + ", keyguardshowing=" + mKeyguardShowing
1561                     + ", provisioned=" + mDeviceProvisioned);
1562         }
1563 
1564         /**
1565          * Get the row ID for an item
1566          * @param position The position of the item within the adapter's data set
1567          * @return
1568          */
1569         public long getItemId(int position) {
1570             return position;
1571         }
1572 
1573         @Override
1574         public View getView(int position, View convertView, ViewGroup parent) {
1575             Action action = getItem(position);
1576             View view = action.create(mContext, convertView, parent, LayoutInflater.from(mContext));
1577             view.setOnClickListener(v -> onClickItem(position));
1578             if (action instanceof LongPressAction) {
1579                 view.setOnLongClickListener(v -> onLongClickItem(position));
1580             }
1581             return view;
1582         }
1583 
1584         @Override
1585         public boolean onLongClickItem(int position) {
1586             final Action action = mAdapter.getItem(position);
1587             if (action instanceof LongPressAction) {
1588                 if (mDialog != null) {
1589                     // Usually clicking an item shuts down the phone, locks, or starts an activity.
1590                     // We don't want to animate back into the power button when that happens, so we
1591                     // disable the dialog animation before dismissing.
1592                     mDialogTransitionAnimator.disableAllCurrentDialogsExitAnimations();
1593                     mDialog.dismiss();
1594                 } else {
1595                     Log.w(TAG, "Action long-clicked while mDialog is null.");
1596                 }
1597                 return ((LongPressAction) action).onLongPress();
1598             }
1599             return false;
1600         }
1601 
1602         @Override
1603         public void onClickItem(int position) {
1604             Action item = mAdapter.getItem(position);
1605             if (!(item instanceof SilentModeTriStateAction)) {
1606                 if (mDialog != null) {
1607                     // don't dismiss the dialog if we're opening the power options menu
1608                     if (!(item instanceof PowerOptionsAction)) {
1609                         // Usually clicking an item shuts down the phone, locks, or starts an
1610                         // activity. We don't want to animate back into the power button when that
1611                         // happens, so we disable the dialog animation before dismissing.
1612                         mDialogTransitionAnimator.disableAllCurrentDialogsExitAnimations();
1613                         mDialog.dismiss();
1614                     }
1615                 } else {
1616                     Log.w(TAG, "Action clicked while mDialog is null.");
1617                 }
1618                 item.onPress();
1619             }
1620         }
1621 
1622         @Override
1623         public boolean shouldBeSeparated(int position) {
1624             return getItem(position).shouldBeSeparated();
1625         }
1626     }
1627 
1628     /**
1629      * The adapter used for items in the overflow menu.
1630      */
1631     public class MyPowerOptionsAdapter extends BaseAdapter {
1632         @Override
1633         public int getCount() {
1634             return mPowerItems.size();
1635         }
1636 
1637         @Override
1638         public Action getItem(int position) {
1639             return mPowerItems.get(position);
1640         }
1641 
1642         @Override
1643         public long getItemId(int position) {
1644             return position;
1645         }
1646 
1647         @Override
1648         public View getView(int position, View convertView, ViewGroup parent) {
1649             Action action = getItem(position);
1650             if (action == null) {
1651                 Log.w(TAG, "No power options action found at position: " + position);
1652                 return null;
1653             }
1654             int viewLayoutResource = com.android.systemui.res.R.layout.global_actions_power_item;
1655             View view = convertView != null ? convertView
1656                     : LayoutInflater.from(mContext).inflate(viewLayoutResource, parent, false);
1657             view.setOnClickListener(v -> onClickItem(position));
1658             if (action instanceof LongPressAction) {
1659                 view.setOnLongClickListener(v -> onLongClickItem(position));
1660             }
1661             ImageView icon = view.findViewById(R.id.icon);
1662             TextView messageView = view.findViewById(R.id.message);
1663             messageView.setSelected(true); // necessary for marquee to work
1664 
1665             icon.setImageDrawable(action.getIcon(mContext));
1666             icon.setScaleType(ScaleType.CENTER_CROP);
1667 
1668             if (action.getMessage() != null) {
1669                 messageView.setText(action.getMessage());
1670             } else {
1671                 messageView.setText(action.getMessageResId());
1672             }
1673             return view;
1674         }
1675 
1676         private boolean onLongClickItem(int position) {
1677             final Action action = getItem(position);
1678             if (action instanceof LongPressAction) {
1679                 if (mDialog != null) {
1680                     // Usually clicking an item shuts down the phone, locks, or starts an activity.
1681                     // We don't want to animate back into the power button when that happens, so we
1682                     // disable the dialog animation before dismissing.
1683                     mDialogTransitionAnimator.disableAllCurrentDialogsExitAnimations();
1684                     mDialog.dismiss();
1685                 } else {
1686                     Log.w(TAG, "Action long-clicked while mDialog is null.");
1687                 }
1688                 return ((LongPressAction) action).onLongPress();
1689             }
1690             return false;
1691         }
1692 
1693         private void onClickItem(int position) {
1694             Action item = getItem(position);
1695             if (!(item instanceof SilentModeTriStateAction)) {
1696                 if (mDialog != null) {
1697                     // Usually clicking an item shuts down the phone, locks, or starts an activity.
1698                     // We don't want to animate back into the power button when that happens, so we
1699                     // disable the dialog animation before dismissing.
1700                     mDialogTransitionAnimator.disableAllCurrentDialogsExitAnimations();
1701                     mDialog.dismiss();
1702                 } else {
1703                     Log.w(TAG, "Action clicked while mDialog is null.");
1704                 }
1705                 item.onPress();
1706             }
1707         }
1708     }
1709 
1710     /**
1711      * The adapter used for items in the power options menu, triggered by the PowerOptionsAction.
1712      */
1713     public class MyOverflowAdapter extends BaseAdapter {
1714         @Override
1715         public int getCount() {
1716             return mOverflowItems.size();
1717         }
1718 
1719         @Override
1720         public Action getItem(int position) {
1721             return mOverflowItems.get(position);
1722         }
1723 
1724         @Override
1725         public long getItemId(int position) {
1726             return position;
1727         }
1728 
1729         @Override
1730         public View getView(int position, View convertView, ViewGroup parent) {
1731             Action action = getItem(position);
1732             if (action == null) {
1733                 Log.w(TAG, "No overflow action found at position: " + position);
1734                 return null;
1735             }
1736             int viewLayoutResource = com.android.systemui.res.R.layout.controls_more_item;
1737             View view = convertView != null ? convertView
1738                     : LayoutInflater.from(mContext).inflate(viewLayoutResource, parent, false);
1739             TextView textView = (TextView) view;
1740             if (action.getMessageResId() != 0) {
1741                 textView.setText(action.getMessageResId());
1742             } else {
1743                 textView.setText(action.getMessage());
1744             }
1745             return textView;
1746         }
1747 
1748         protected boolean onLongClickItem(int position) {
1749             final Action action = getItem(position);
1750             if (action instanceof LongPressAction) {
1751                 if (mDialog != null) {
1752                     // Usually clicking an item shuts down the phone, locks, or starts an activity.
1753                     // We don't want to animate back into the power button when that happens, so we
1754                     // disable the dialog animation before dismissing.
1755                     mDialogTransitionAnimator.disableAllCurrentDialogsExitAnimations();
1756                     mDialog.dismiss();
1757                 } else {
1758                     Log.w(TAG, "Action long-clicked while mDialog is null.");
1759                 }
1760                 return ((LongPressAction) action).onLongPress();
1761             }
1762             return false;
1763         }
1764 
1765         protected void onClickItem(int position) {
1766             Action item = getItem(position);
1767             if (!(item instanceof SilentModeTriStateAction)) {
1768                 if (mDialog != null) {
1769                     // Usually clicking an item shuts down the phone, locks, or starts an activity.
1770                     // We don't want to animate back into the power button when that happens, so we
1771                     // disable the dialog animation before dismissing.
1772                     mDialogTransitionAnimator.disableAllCurrentDialogsExitAnimations();
1773                     mDialog.dismiss();
1774                 } else {
1775                     Log.w(TAG, "Action clicked while mDialog is null.");
1776                 }
1777                 item.onPress();
1778             }
1779         }
1780     }
1781 
1782     // note: the scheme below made more sense when we were planning on having
1783     // 8 different things in the global actions dialog.  seems overkill with
1784     // only 3 items now, but may as well keep this flexible approach so it will
1785     // be easy should someone decide at the last minute to include something
1786     // else, such as 'enable wifi', or 'enable bluetooth'
1787 
1788     /**
1789      * What each item in the global actions dialog must be able to support.
1790      */
1791     public interface Action {
1792         /**
1793          * @return Text that will be announced when dialog is created.  null for none.
1794          */
1795         CharSequence getLabelForAccessibility(Context context);
1796 
1797         /**
1798          * Create the item's view
1799          * @param context
1800          * @param convertView
1801          * @param parent
1802          * @param inflater
1803          * @return
1804          */
1805         View create(Context context, View convertView, ViewGroup parent, LayoutInflater inflater);
1806 
1807         /**
1808          * Handle a regular press
1809          */
1810         void onPress();
1811 
1812         /**
1813          * @return whether this action should appear in the dialog when the keygaurd is showing.
1814          */
1815         boolean showDuringKeyguard();
1816 
1817         /**
1818          * @return whether this action should appear in the dialog before the
1819          * device is provisioned.f
1820          */
1821         boolean showBeforeProvisioning();
1822 
1823         /**
1824          * @return whether this action is enabled
1825          */
1826         boolean isEnabled();
1827 
1828         /**
1829          * @return whether this action should be in a separate section
1830          */
1831         default boolean shouldBeSeparated() {
1832             return false;
1833         }
1834 
1835         /**
1836          * Return the id of the message associated with this action, or 0 if it doesn't have one.
1837          * @return
1838          */
1839         int getMessageResId();
1840 
1841         /**
1842          * Return the icon drawable for this action.
1843          */
1844         Drawable getIcon(Context context);
1845 
1846         /**
1847          * Return the message associated with this action, or null if it doesn't have one.
1848          * @return
1849          */
1850         CharSequence getMessage();
1851 
1852         /**
1853          * @return whether the action should be visible
1854          */
1855         default boolean shouldShow() {
1856             return true;
1857         }
1858     }
1859 
1860     /**
1861      * An action that also supports long press.
1862      */
1863     private interface LongPressAction extends Action {
1864         boolean onLongPress();
1865     }
1866 
1867     /**
1868      * A single press action maintains no state, just responds to a press and takes an action.
1869      */
1870 
1871     @VisibleForTesting
1872     abstract class SinglePressAction implements Action {
1873         private final int mIconResId;
1874         private final Drawable mIcon;
1875         private final int mMessageResId;
1876         private final CharSequence mMessage;
1877         @VisibleForTesting ImageView mIconView;
1878 
1879         protected SinglePressAction(int iconResId, int messageResId) {
1880             mIconResId = iconResId;
1881             mMessageResId = messageResId;
1882             mMessage = null;
1883             mIcon = null;
1884             mIconView = null;
1885         }
1886 
1887         protected SinglePressAction(int iconResId, Drawable icon, CharSequence message) {
1888             mIconResId = iconResId;
1889             mMessageResId = 0;
1890             mMessage = message;
1891             mIcon = icon;
1892         }
1893 
1894         public boolean isEnabled() {
1895             return true;
1896         }
1897 
1898         public String getStatus() {
1899             return null;
1900         }
1901 
1902         public abstract void onPress();
1903 
1904         public CharSequence getLabelForAccessibility(Context context) {
1905             if (mMessage != null) {
1906                 return mMessage;
1907             } else {
1908                 return context.getString(mMessageResId);
1909             }
1910         }
1911 
1912         public int getMessageResId() {
1913             return mMessageResId;
1914         }
1915 
1916         public CharSequence getMessage() {
1917             return mMessage;
1918         }
1919 
1920         @Override
1921         public Drawable getIcon(Context context) {
1922             if (mIcon != null) {
1923                 return mIcon;
1924             } else {
1925                 return context.getDrawable(mIconResId);
1926             }
1927         }
1928 
1929         public View create(
1930                 Context context, View convertView, ViewGroup parent, LayoutInflater inflater) {
1931             View v = inflater.inflate(getGridItemLayoutResource(), parent, false /* attach */);
1932             // ConstraintLayout flow needs an ID to reference
1933             v.setId(View.generateViewId());
1934 
1935             mIconView = v.findViewById(R.id.icon);
1936             TextView messageView = v.findViewById(R.id.message);
1937             messageView.setSelected(true); // necessary for marquee to work
1938 
1939             mIconView.setImageDrawable(getIcon(context));
1940             mIconView.setScaleType(ScaleType.CENTER_CROP);
1941             if (com.android.systemui.Flags.tvGlobalActionsFocus()) {
1942                 if (isTv()) {
1943                     mIconView.setFocusable(true);
1944                     mIconView.setClickable(true);
1945                     mIconView.setBackground(mContext.getDrawable(com.android.systemui.res.R.drawable
1946                                     .global_actions_lite_button_background));
1947                     mIconView.setOnClickListener(i -> onClick());
1948                     if (mItems.get(0) == this) {
1949                         mIconView.requestFocus();
1950                     }
1951                 }
1952             }
1953 
1954             if (mMessage != null) {
1955                 messageView.setText(mMessage);
1956             } else {
1957                 messageView.setText(mMessageResId);
1958             }
1959 
1960             return v;
1961         }
1962 
1963         private void onClick() {
1964             if (mDialog != null) {
1965                 // don't dismiss the dialog if we're opening the power options menu
1966                 if (!(this instanceof PowerOptionsAction)) {
1967                     // Usually clicking an item shuts down the phone, locks, or starts an
1968                     // activity. We don't want to animate back into the power button when that
1969                     // happens, so we disable the dialog animation before dismissing.
1970                     mDialogTransitionAnimator.disableAllCurrentDialogsExitAnimations();
1971                     mDialog.dismiss();
1972                 }
1973             } else {
1974                 Log.w(TAG, "Action icon clicked while mDialog is null.");
1975             }
1976             onPress();
1977         }
1978     }
1979 
1980     protected int getGridItemLayoutResource() {
1981         return com.android.systemui.res.R.layout.global_actions_grid_item_lite;
1982     }
1983 
1984     private enum ToggleState {
1985         Off(false),
1986         TurningOn(true),
1987         TurningOff(true),
1988         On(false);
1989 
1990         private final boolean mInTransition;
1991 
1992         ToggleState(boolean intermediate) {
1993             mInTransition = intermediate;
1994         }
1995 
1996         public boolean inTransition() {
1997             return mInTransition;
1998         }
1999     }
2000 
2001     /**
2002      * A toggle action knows whether it is on or off, and displays an icon and status message
2003      * accordingly.
2004      */
2005     private abstract class ToggleAction implements Action {
2006 
2007         protected ToggleState mState = ToggleState.Off;
2008 
2009         // prefs
2010         protected int mEnabledIconResId;
2011         protected int mDisabledIconResid;
2012         protected int mMessageResId;
2013         protected int mEnabledStatusMessageResId;
2014         protected int mDisabledStatusMessageResId;
2015 
2016         /**
2017          * @param enabledIconResId           The icon for when this action is on.
2018          * @param disabledIconResid          The icon for when this action is off.
2019          * @param message                    The general information message, e.g 'Silent Mode'
2020          * @param enabledStatusMessageResId  The on status message, e.g 'sound disabled'
2021          * @param disabledStatusMessageResId The off status message, e.g. 'sound enabled'
2022          */
2023         ToggleAction(int enabledIconResId,
2024                 int disabledIconResid,
2025                 int message,
2026                 int enabledStatusMessageResId,
2027                 int disabledStatusMessageResId) {
2028             mEnabledIconResId = enabledIconResId;
2029             mDisabledIconResid = disabledIconResid;
2030             mMessageResId = message;
2031             mEnabledStatusMessageResId = enabledStatusMessageResId;
2032             mDisabledStatusMessageResId = disabledStatusMessageResId;
2033         }
2034 
2035         /**
2036          * Override to make changes to resource IDs just before creating the View.
2037          */
2038         void willCreate() {
2039 
2040         }
2041 
2042         @Override
2043         public CharSequence getLabelForAccessibility(Context context) {
2044             return context.getString(mMessageResId);
2045         }
2046 
2047         private boolean isOn() {
2048             return mState == ToggleState.On || mState == ToggleState.TurningOn;
2049         }
2050 
2051         @Override
2052         public CharSequence getMessage() {
2053             return null;
2054         }
2055         @Override
2056         public int getMessageResId() {
2057             return isOn() ? mEnabledStatusMessageResId : mDisabledStatusMessageResId;
2058         }
2059 
2060         private int getIconResId() {
2061             return isOn() ? mEnabledIconResId : mDisabledIconResid;
2062         }
2063 
2064         @Override
2065         public Drawable getIcon(Context context) {
2066             return context.getDrawable(getIconResId());
2067         }
2068 
2069         public View create(Context context, View convertView, ViewGroup parent,
2070                 LayoutInflater inflater) {
2071             willCreate();
2072 
2073             View v = inflater.inflate(com.android.systemui.res.R.layout.global_actions_grid_item_v2,
2074                     parent, false /* attach */);
2075             ViewGroup.LayoutParams p = v.getLayoutParams();
2076             p.width = WRAP_CONTENT;
2077             v.setLayoutParams(p);
2078 
2079             ImageView icon = (ImageView) v.findViewById(R.id.icon);
2080             TextView messageView = (TextView) v.findViewById(R.id.message);
2081             final boolean enabled = isEnabled();
2082 
2083             if (messageView != null) {
2084                 messageView.setText(getMessageResId());
2085                 messageView.setEnabled(enabled);
2086                 messageView.setSelected(true); // necessary for marquee to work
2087             }
2088 
2089             if (icon != null) {
2090                 icon.setImageDrawable(context.getDrawable(getIconResId()));
2091                 icon.setEnabled(enabled);
2092             }
2093 
2094             v.setEnabled(enabled);
2095 
2096             return v;
2097         }
2098 
2099         public final void onPress() {
2100             if (mState.inTransition()) {
2101                 Log.w(TAG, "shouldn't be able to toggle when in transition");
2102                 return;
2103             }
2104 
2105             final boolean nowOn = !(mState == ToggleState.On);
2106             onToggle(nowOn);
2107             changeStateFromPress(nowOn);
2108         }
2109 
2110         public boolean isEnabled() {
2111             return !mState.inTransition();
2112         }
2113 
2114         /**
2115          * Implementations may override this if their state can be in on of the intermediate states
2116          * until some notification is received (e.g airplane mode is 'turning off' until we know the
2117          * wireless connections are back online
2118          *
2119          * @param buttonOn Whether the button was turned on or off
2120          */
2121         protected void changeStateFromPress(boolean buttonOn) {
2122             mState = buttonOn ? ToggleState.On : ToggleState.Off;
2123         }
2124 
2125         abstract void onToggle(boolean on);
2126 
2127         public void updateState(ToggleState state) {
2128             mState = state;
2129         }
2130     }
2131 
2132     private class AirplaneModeAction extends ToggleAction {
2133         AirplaneModeAction() {
2134             super(
2135                     R.drawable.ic_lock_airplane_mode,
2136                     R.drawable.ic_lock_airplane_mode_off,
2137                     R.string.global_actions_toggle_airplane_mode,
2138                     R.string.global_actions_airplane_mode_on_status,
2139                     R.string.global_actions_airplane_mode_off_status);
2140         }
2141 
2142         void onToggle(boolean on) {
2143             if (mHasTelephony && TelephonyProperties.in_ecm_mode().orElse(false)) {
2144                 mIsWaitingForEcmExit = true;
2145                 // Launch ECM exit dialog
2146                 Intent ecmDialogIntent =
2147                         new Intent(TelephonyManager.ACTION_SHOW_NOTICE_ECM_BLOCK_OTHERS, null);
2148                 ecmDialogIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
2149                 mContext.startActivity(ecmDialogIntent);
2150             } else {
2151                 changeAirplaneModeSystemSetting(on);
2152             }
2153         }
2154 
2155         @Override
2156         protected void changeStateFromPress(boolean buttonOn) {
2157             if (!mHasTelephony) return;
2158 
2159             // In ECM mode airplane state cannot be changed
2160             if (!TelephonyProperties.in_ecm_mode().orElse(false)) {
2161                 mState = buttonOn ? ToggleState.TurningOn : ToggleState.TurningOff;
2162                 mAirplaneState = mState;
2163             }
2164         }
2165 
2166         public boolean showDuringKeyguard() {
2167             return true;
2168         }
2169 
2170         public boolean showBeforeProvisioning() {
2171             return false;
2172         }
2173     }
2174 
2175     private class SilentModeToggleAction extends ToggleAction {
2176         SilentModeToggleAction() {
2177             super(R.drawable.ic_audio_vol_mute,
2178                     R.drawable.ic_audio_vol,
2179                     R.string.global_action_toggle_silent_mode,
2180                     R.string.global_action_silent_mode_on_status,
2181                     R.string.global_action_silent_mode_off_status);
2182         }
2183 
2184         void onToggle(boolean on) {
2185             if (on) {
2186                 mAudioManager.setRingerMode(AudioManager.RINGER_MODE_SILENT);
2187             } else {
2188                 mAudioManager.setRingerMode(AudioManager.RINGER_MODE_NORMAL);
2189             }
2190         }
2191 
2192         public boolean showDuringKeyguard() {
2193             return true;
2194         }
2195 
2196         public boolean showBeforeProvisioning() {
2197             return false;
2198         }
2199     }
2200 
2201     private static class SilentModeTriStateAction implements Action, View.OnClickListener {
2202 
2203         private static final int[] ITEM_IDS = {R.id.option1, R.id.option2, R.id.option3};
2204 
2205         private final AudioManager mAudioManager;
2206         private final Handler mHandler;
2207 
2208         SilentModeTriStateAction(AudioManager audioManager, Handler handler) {
2209             mAudioManager = audioManager;
2210             mHandler = handler;
2211         }
2212 
2213         private int ringerModeToIndex(int ringerMode) {
2214             // They just happen to coincide
2215             return ringerMode;
2216         }
2217 
2218         private int indexToRingerMode(int index) {
2219             // They just happen to coincide
2220             return index;
2221         }
2222 
2223         @Override
2224         public CharSequence getLabelForAccessibility(Context context) {
2225             return null;
2226         }
2227 
2228         @Override
2229         public int getMessageResId() {
2230             return 0;
2231         }
2232 
2233         @Override
2234         public CharSequence getMessage() {
2235             return null;
2236         }
2237 
2238         @Override
2239         public Drawable getIcon(Context context) {
2240             return null;
2241         }
2242 
2243 
2244         public View create(Context context, View convertView, ViewGroup parent,
2245                 LayoutInflater inflater) {
2246             View v = inflater.inflate(R.layout.global_actions_silent_mode, parent, false);
2247 
2248             int selectedIndex = ringerModeToIndex(mAudioManager.getRingerMode());
2249             for (int i = 0; i < 3; i++) {
2250                 View itemView = v.findViewById(ITEM_IDS[i]);
2251                 itemView.setSelected(selectedIndex == i);
2252                 // Set up click handler
2253                 itemView.setTag(i);
2254                 itemView.setOnClickListener(this);
2255             }
2256             return v;
2257         }
2258 
2259         public void onPress() {
2260         }
2261 
2262         public boolean showDuringKeyguard() {
2263             return true;
2264         }
2265 
2266         public boolean showBeforeProvisioning() {
2267             return false;
2268         }
2269 
2270         public boolean isEnabled() {
2271             return true;
2272         }
2273 
2274         void willCreate() {
2275         }
2276 
2277         public void onClick(View v) {
2278             if (!(v.getTag() instanceof Integer)) return;
2279 
2280             int index = (Integer) v.getTag();
2281             mAudioManager.setRingerMode(indexToRingerMode(index));
2282             mHandler.sendEmptyMessageDelayed(MESSAGE_DISMISS, DIALOG_DISMISS_DELAY);
2283         }
2284     }
2285 
2286     private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
2287         public void onReceive(Context context, Intent intent) {
2288             String action = intent.getAction();
2289             if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action)
2290                     || Intent.ACTION_SCREEN_OFF.equals(action)) {
2291                 String reason = intent.getStringExtra(SYSTEM_DIALOG_REASON_KEY);
2292                 if (!SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS.equals(reason)) {
2293                     // These broadcasts are usually received when locking the device, swiping up to
2294                     // home (which collapses the shade), etc. In those cases, we usually don't want
2295                     // to animate this dialog back into the view, so we disable the exit animations.
2296                     mDialogTransitionAnimator.disableAllCurrentDialogsExitAnimations();
2297                     mHandler.sendMessage(mHandler.obtainMessage(MESSAGE_DISMISS, reason));
2298                 }
2299             } else if (TelephonyManager.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED.equals(action)) {
2300                 // Airplane mode can be changed after ECM exits if airplane toggle button
2301                 // is pressed during ECM mode
2302                 if (!(intent.getBooleanExtra(TelephonyManager.EXTRA_PHONE_IN_ECM_STATE, false))
2303                         && mIsWaitingForEcmExit) {
2304                     mIsWaitingForEcmExit = false;
2305                     changeAirplaneModeSystemSetting(true);
2306                 }
2307             }
2308         }
2309     };
2310 
2311     private final TelephonyCallback.ServiceStateListener mPhoneStateListener =
2312             new TelephonyCallback.ServiceStateListener() {
2313         @Override
2314         public void onServiceStateChanged(ServiceState serviceState) {
2315             if (!mHasTelephony) return;
2316             if (mAirplaneModeOn == null) {
2317                 Log.d(TAG, "Service changed before actions created");
2318                 return;
2319             }
2320             final boolean inAirplaneMode = serviceState.getState() == ServiceState.STATE_POWER_OFF;
2321             mAirplaneState = inAirplaneMode ? ToggleState.On : ToggleState.Off;
2322             mAirplaneModeOn.updateState(mAirplaneState);
2323             mAdapter.notifyDataSetChanged();
2324             mOverflowAdapter.notifyDataSetChanged();
2325             mPowerAdapter.notifyDataSetChanged();
2326         }
2327     };
2328 
2329     private final ContentObserver mAirplaneModeObserver = new ContentObserver(mMainHandler) {
2330         @Override
2331         public void onChange(boolean selfChange) {
2332             onAirplaneModeChanged();
2333         }
2334     };
2335 
2336     private static final int MESSAGE_DISMISS = 0;
2337     private static final int MESSAGE_REFRESH = 1;
2338     private static final int DIALOG_DISMISS_DELAY = 300; // ms
2339     private static final int DIALOG_PRESS_DELAY = 850; // ms
2340 
2341     @VisibleForTesting void setZeroDialogPressDelayForTesting() {
2342         mDialogPressDelay = 0; // ms
2343     }
2344 
2345     private void onAirplaneModeChanged() {
2346         // Let the service state callbacks handle the state.
2347         if (mHasTelephony || mAirplaneModeOn == null) return;
2348 
2349         boolean airplaneModeOn = mGlobalSettings.getInt(
2350                 Settings.Global.AIRPLANE_MODE_ON,
2351                 0) == 1;
2352         mAirplaneState = airplaneModeOn ? ToggleState.On : ToggleState.Off;
2353         mAirplaneModeOn.updateState(mAirplaneState);
2354     }
2355 
2356     /**
2357      * Change the airplane mode system setting
2358      */
2359     private void changeAirplaneModeSystemSetting(boolean on) {
2360         mGlobalSettings.putInt(Settings.Global.AIRPLANE_MODE_ON, on ? 1 : 0);
2361         Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
2362         intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
2363         intent.putExtra("state", on);
2364         mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
2365         if (!mHasTelephony) {
2366             mAirplaneState = on ? ToggleState.On : ToggleState.Off;
2367         }
2368     }
2369 
2370     @NonNull
2371     @Override
2372     public Lifecycle getLifecycle() {
2373         return mLifecycle;
2374     }
2375 
2376     @VisibleForTesting
2377     static class ActionsDialogLite extends SystemUIDialog implements DialogInterface,
2378             ColorExtractor.OnColorsChangedListener {
2379 
2380         protected final Context mContext;
2381         protected MultiListLayout mGlobalActionsLayout;
2382         protected final MyAdapter mAdapter;
2383         protected final MyOverflowAdapter mOverflowAdapter;
2384         protected final MyPowerOptionsAdapter mPowerOptionsAdapter;
2385         protected final IStatusBarService mStatusBarService;
2386         protected final IBinder mToken = new Binder();
2387         protected Drawable mBackgroundDrawable;
2388         protected final SysuiColorExtractor mColorExtractor;
2389         private boolean mKeyguardShowing;
2390         protected float mScrimAlpha;
2391         protected final LightBarController mLightBarController;
2392         private final KeyguardStateController mKeyguardStateController;
2393         protected final NotificationShadeWindowController mNotificationShadeWindowController;
2394         private final StatusBarWindowController mStatusBarWindowController;
2395         private ListPopupWindow mOverflowPopup;
2396         private Dialog mPowerOptionsDialog;
2397         protected final Runnable mOnRefreshCallback;
2398         private UiEventLogger mUiEventLogger;
2399         private GestureDetector mGestureDetector;
2400         private final ShadeController mShadeController;
2401         private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
2402         private SelectedUserInteractor mSelectedUserInteractor;
2403         private LockPatternUtils mLockPatternUtils;
2404         private float mWindowDimAmount;
2405 
2406         protected ViewGroup mContainer;
2407 
2408         private final OnBackInvokedCallback mOnBackInvokedCallback = () -> {
2409             logOnBackInvocation();
2410             dismiss();
2411         };
2412 
2413         @VisibleForTesting
2414         protected GestureDetector.SimpleOnGestureListener mGestureListener =
2415                 new GestureDetector.SimpleOnGestureListener() {
2416                     @Override
2417                     public boolean onDown(MotionEvent e) {
2418                         // All gestures begin with this message, so continue listening
2419                         return true;
2420                     }
2421 
2422                     @Override
2423                     public boolean onSingleTapUp(MotionEvent e) {
2424                         // Close without opening shade
2425                         mUiEventLogger.log(GlobalActionsEvent.GA_CLOSE_TAP_OUTSIDE);
2426                         cancel();
2427                         return false;
2428                     }
2429 
2430                     @Override
2431                     public boolean onScroll(@Nullable MotionEvent e1, MotionEvent e2,
2432                             float distanceX,
2433                             float distanceY) {
2434                         if (distanceY < 0 && distanceY > distanceX
2435                                 && e1 != null
2436                                 && e1.getY() <= mStatusBarWindowController.getStatusBarHeight()) {
2437                             // Downwards scroll from top
2438                             openShadeAndDismiss();
2439                             return true;
2440                         }
2441                         return false;
2442                     }
2443 
2444                     @Override
2445                     public boolean onFling(@Nullable MotionEvent e1, MotionEvent e2,
2446                             float velocityX,
2447                             float velocityY) {
2448                         if (velocityY > 0 && Math.abs(velocityY) > Math.abs(velocityX)
2449                                 && e1 != null
2450                                 && e1.getY() <= mStatusBarWindowController.getStatusBarHeight()) {
2451                             // Downwards fling from top
2452                             openShadeAndDismiss();
2453                             return true;
2454                         }
2455                         return false;
2456                     }
2457                 };
2458 
2459 
2460         // this exists so that we can point it to a mock during Unit Testing
2461         private OnBackInvokedDispatcher mOverriddenBackDispatcher;
2462 
2463         // the following method exists so that a Unit Test can supply a `OnBackInvokedDispatcher`
2464         @VisibleForTesting
2465         void setBackDispatcherOverride(OnBackInvokedDispatcher mockDispatcher) {
2466             mOverriddenBackDispatcher = mockDispatcher;
2467         }
2468 
2469         ActionsDialogLite(Context context,
2470                 int themeRes,
2471                 MyAdapter adapter,
2472                 MyOverflowAdapter overflowAdapter,
2473                 SysuiColorExtractor sysuiColorExtractor,
2474                 IStatusBarService statusBarService,
2475                 LightBarController lightBarController,
2476                 KeyguardStateController keyguardStateController,
2477                 NotificationShadeWindowController notificationShadeWindowController,
2478                 StatusBarWindowController statusBarWindowController,
2479                 Runnable onRefreshCallback,
2480                 boolean keyguardShowing,
2481                 MyPowerOptionsAdapter powerAdapter,
2482                 UiEventLogger uiEventLogger,
2483                 ShadeController shadeController,
2484                 KeyguardUpdateMonitor keyguardUpdateMonitor,
2485                 LockPatternUtils lockPatternUtils,
2486                 SelectedUserInteractor selectedUserInteractor) {
2487             // We set dismissOnDeviceLock to false because we have a custom broadcast receiver to
2488             // dismiss this dialog when the device is locked.
2489             super(context, themeRes, false /* dismissOnDeviceLock */);
2490             mContext = context;
2491             mAdapter = adapter;
2492             mOverflowAdapter = overflowAdapter;
2493             mPowerOptionsAdapter = powerAdapter;
2494             mColorExtractor = sysuiColorExtractor;
2495             mStatusBarService = statusBarService;
2496             mLightBarController = lightBarController;
2497             mKeyguardStateController = keyguardStateController;
2498             mNotificationShadeWindowController = notificationShadeWindowController;
2499             mStatusBarWindowController = statusBarWindowController;
2500             mOnRefreshCallback = onRefreshCallback;
2501             mKeyguardShowing = keyguardShowing;
2502             mUiEventLogger = uiEventLogger;
2503             mShadeController = shadeController;
2504             mKeyguardUpdateMonitor = keyguardUpdateMonitor;
2505             mLockPatternUtils = lockPatternUtils;
2506             mGestureDetector = new GestureDetector(mContext, mGestureListener);
2507             mSelectedUserInteractor = selectedUserInteractor;
2508         }
2509 
2510         @Override
2511         protected void onCreate(Bundle savedInstanceState) {
2512             super.onCreate(savedInstanceState);
2513             getWindow().setTitle(getContext().getString(
2514                     com.android.systemui.res.R.string.accessibility_quick_settings_power_menu));
2515             initializeLayout();
2516             mWindowDimAmount = getWindow().getAttributes().dimAmount;
2517             getOnBackInvokedDispatcher().registerOnBackInvokedCallback(
2518                     OnBackInvokedDispatcher.PRIORITY_DEFAULT, mOnBackInvokedCallback);
2519             if (DEBUG) Log.d(TAG, "OnBackInvokedCallback handler registered");
2520         }
2521 
2522         @VisibleForTesting
2523         @Override
2524         public OnBackInvokedDispatcher getOnBackInvokedDispatcher() {
2525             if (mOverriddenBackDispatcher != null) return mOverriddenBackDispatcher;
2526             else return super.getOnBackInvokedDispatcher();
2527         }
2528 
2529         @Override
2530         public void onDetachedFromWindow() {
2531             getOnBackInvokedDispatcher().unregisterOnBackInvokedCallback(mOnBackInvokedCallback);
2532             if (DEBUG) Log.d(TAG, "OnBackInvokedCallback handler unregistered");
2533         }
2534 
2535         @Override
2536         protected int getWidth() {
2537             return MATCH_PARENT;
2538         }
2539 
2540         @Override
2541         protected int getHeight() {
2542             return MATCH_PARENT;
2543         }
2544 
2545         @Override
2546         public boolean onTouchEvent(MotionEvent event) {
2547             return mGestureDetector.onTouchEvent(event) || super.onTouchEvent(event);
2548         }
2549 
2550         private void openShadeAndDismiss() {
2551             mUiEventLogger.log(GlobalActionsEvent.GA_CLOSE_TAP_OUTSIDE);
2552             if (mKeyguardStateController.isShowing()) {
2553                 // match existing lockscreen behavior to open QS when swiping from status bar
2554                 mShadeController.animateExpandQs();
2555             } else {
2556                 // otherwise, swiping down should expand notification shade
2557                 mShadeController.animateExpandShade();
2558             }
2559             dismiss();
2560         }
2561 
2562         private ListPopupWindow createPowerOverflowPopup() {
2563             GlobalActionsPopupMenu popup = new GlobalActionsPopupMenu(
2564                     new ContextThemeWrapper(
2565                             mContext,
2566                             com.android.systemui.res.R.style.Control_ListPopupWindow
2567                     ), false /* isDropDownMode */);
2568             popup.setOnItemClickListener(
2569                     (parent, view, position, id) -> mOverflowAdapter.onClickItem(position));
2570             popup.setOnItemLongClickListener(
2571                     (parent, view, position, id) -> mOverflowAdapter.onLongClickItem(position));
2572             View overflowButton =
2573                     findViewById(com.android.systemui.res.R.id.global_actions_overflow_button);
2574             popup.setAnchorView(overflowButton);
2575             popup.setAdapter(mOverflowAdapter);
2576             return popup;
2577         }
2578 
2579         public void showPowerOptionsMenu() {
2580             mPowerOptionsDialog = GlobalActionsPowerDialog.create(mContext, mPowerOptionsAdapter);
2581             mPowerOptionsDialog.show();
2582         }
2583 
2584         protected void showPowerOverflowMenu() {
2585             mOverflowPopup = createPowerOverflowPopup();
2586             mOverflowPopup.show();
2587         }
2588 
2589         protected int getLayoutResource() {
2590             return com.android.systemui.res.R.layout.global_actions_grid_lite;
2591         }
2592 
2593         protected void initializeLayout() {
2594             setContentView(getLayoutResource());
2595             fixNavBarClipping();
2596 
2597             mGlobalActionsLayout = findViewById(com.android.systemui.res.R.id.global_actions_view);
2598             mGlobalActionsLayout.setListViewAccessibilityDelegate(new View.AccessibilityDelegate() {
2599                 @Override
2600                 public boolean dispatchPopulateAccessibilityEvent(
2601                         View host, AccessibilityEvent event) {
2602                     // Populate the title here, just as Activity does
2603                     event.getText().add(mContext.getString(R.string.global_actions));
2604                     return true;
2605                 }
2606             });
2607             mGlobalActionsLayout.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
2608             mGlobalActionsLayout.setRotationListener(this::onRotate);
2609             mGlobalActionsLayout.setAdapter(mAdapter);
2610             mContainer = findViewById(com.android.systemui.res.R.id.global_actions_container);
2611             mContainer.setOnTouchListener((v, event) -> {
2612                 mGestureDetector.onTouchEvent(event);
2613                 return v.onTouchEvent(event);
2614             });
2615 
2616             View overflowButton = findViewById(
2617                     com.android.systemui.res.R.id.global_actions_overflow_button);
2618             if (overflowButton != null) {
2619                 if (mOverflowAdapter.getCount() > 0) {
2620                     overflowButton.setOnClickListener((view) -> showPowerOverflowMenu());
2621                     LinearLayout.LayoutParams params =
2622                             (LinearLayout.LayoutParams) mGlobalActionsLayout.getLayoutParams();
2623                     params.setMarginEnd(0);
2624                     mGlobalActionsLayout.setLayoutParams(params);
2625                 } else {
2626                     overflowButton.setVisibility(View.GONE);
2627                     LinearLayout.LayoutParams params =
2628                             (LinearLayout.LayoutParams) mGlobalActionsLayout.getLayoutParams();
2629                     params.setMarginEnd(mContext.getResources().getDimensionPixelSize(
2630                             com.android.systemui.res.R.dimen.global_actions_side_margin));
2631                     mGlobalActionsLayout.setLayoutParams(params);
2632                 }
2633             }
2634 
2635             if (mBackgroundDrawable == null) {
2636                 mBackgroundDrawable = new ScrimDrawable();
2637                 mScrimAlpha = 1.0f;
2638             }
2639 
2640             // If user entered from the lock screen and smart lock was enabled, disable it
2641             int user = mSelectedUserInteractor.getSelectedUserId();
2642             boolean userHasTrust = mKeyguardUpdateMonitor.getUserHasTrust(user);
2643             if (mKeyguardShowing && userHasTrust) {
2644                 mLockPatternUtils.requireCredentialEntry(user);
2645                 showSmartLockDisabledMessage();
2646             }
2647         }
2648 
2649         protected void fixNavBarClipping() {
2650             ViewGroup content = findViewById(android.R.id.content);
2651             content.setClipChildren(false);
2652             content.setClipToPadding(false);
2653             ViewGroup contentParent = (ViewGroup) content.getParent();
2654             contentParent.setClipChildren(false);
2655             contentParent.setClipToPadding(false);
2656         }
2657 
2658         private void showSmartLockDisabledMessage() {
2659             // Since power menu is the top window, make a Toast-like view that will show up
2660             View message = LayoutInflater.from(mContext)
2661                     .inflate(com.android.systemui.res.R.layout.global_actions_toast, mContainer, false);
2662 
2663             // Set up animation
2664             AccessibilityManager mAccessibilityManager =
2665                     (AccessibilityManager) getContext().getSystemService(
2666                             Context.ACCESSIBILITY_SERVICE);
2667             final int visibleTime = mAccessibilityManager.getRecommendedTimeoutMillis(
2668                     TOAST_VISIBLE_TIME, AccessibilityManager.FLAG_CONTENT_TEXT);
2669             message.setVisibility(View.VISIBLE);
2670             message.setAlpha(0f);
2671             mContainer.addView(message);
2672 
2673             // Fade in
2674             message.animate()
2675                     .alpha(1f)
2676                     .setDuration(TOAST_FADE_TIME)
2677                     .setListener(new AnimatorListenerAdapter() {
2678                         @Override
2679                         public void onAnimationEnd(Animator animation) {
2680                             // Then fade out
2681                             message.animate()
2682                                     .alpha(0f)
2683                                     .setDuration(TOAST_FADE_TIME)
2684                                     .setStartDelay(visibleTime)
2685                                     .setListener(null);
2686                         }
2687                     });
2688         }
2689 
2690         @Override
2691         protected void start() {
2692             mGlobalActionsLayout.updateList();
2693             mLightBarController.setGlobalActionsVisible(true);
2694 
2695             if (mBackgroundDrawable instanceof ScrimDrawable) {
2696                 mColorExtractor.addOnColorsChangedListener(this);
2697                 GradientColors colors = mColorExtractor.getNeutralColors();
2698                 updateColors(colors, false /* animate */);
2699             }
2700         }
2701 
2702         /**
2703          * Updates background and system bars according to current GradientColors.
2704          *
2705          * @param colors  Colors and hints to use.
2706          * @param animate Interpolates gradient if true, just sets otherwise.
2707          */
2708         private void updateColors(GradientColors colors, boolean animate) {
2709             if (!(mBackgroundDrawable instanceof ScrimDrawable)) {
2710                 return;
2711             }
2712             ((ScrimDrawable) mBackgroundDrawable).setColor(Color.BLACK, animate);
2713             View decorView = getWindow().getDecorView();
2714             if (colors.supportsDarkText()) {
2715                 decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR
2716                         | View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
2717             } else {
2718                 decorView.setSystemUiVisibility(0);
2719             }
2720         }
2721 
2722         @Override
2723         protected void stop() {
2724             mLightBarController.setGlobalActionsVisible(false);
2725             mColorExtractor.removeOnColorsChangedListener(this);
2726         }
2727 
2728         @Override
2729         public void onBackPressed() {
2730             super.onBackPressed();
2731             logOnBackInvocation();
2732         }
2733 
2734         private void logOnBackInvocation() {
2735             mUiEventLogger.log(GlobalActionsEvent.GA_CLOSE_BACK);
2736             if (DEBUG) Log.d(TAG, "onBack invoked");
2737         }
2738 
2739         @Override
2740         public void show() {
2741             super.show();
2742             mNotificationShadeWindowController.setRequestTopUi(true, TAG);
2743 
2744             // By default this dialog windowAnimationStyle is null, and therefore windowAnimations
2745             // should be equal to 0 which means we need to animate the dialog in-window. If it's not
2746             // equal to 0, it means it has been overridden to animate (e.g. by the
2747             // DialogTransitionAnimator) so we don't run the animation.
2748             boolean shouldAnimateInWindow = getWindow().getAttributes().windowAnimations == 0;
2749             if (shouldAnimateInWindow) {
2750                 startAnimation(true /* isEnter */, null /* then */);
2751 
2752                 // Override the dialog dismiss so that we can animate in-window before dismissing
2753                 // the dialog.
2754                 setDismissOverride(() -> {
2755                     startAnimation(false /* isEnter */, /* then */ () -> {
2756                         setDismissOverride(null);
2757 
2758                         // Hide then dismiss to instantly dismiss.
2759                         hide();
2760                         dismiss();
2761                     });
2762                 });
2763             }
2764         }
2765 
2766         /** Run either the enter or exit animation, then run {@code then}. */
2767         private void startAnimation(boolean isEnter, @Nullable Runnable then) {
2768             ValueAnimator animator = ValueAnimator.ofFloat(0f, 1f);
2769 
2770             // Note: these specs should be the same as in popup_enter_material and
2771             // popup_exit_material.
2772             float translationPx;
2773             Resources resources = getContext().getResources();
2774             if (isEnter) {
2775                 translationPx = resources.getDimension(R.dimen.popup_enter_animation_from_y_delta);
2776                 animator.setInterpolator(Interpolators.STANDARD);
2777                 animator.setDuration(resources.getInteger(R.integer.config_activityDefaultDur));
2778             } else {
2779                 translationPx = resources.getDimension(R.dimen.popup_exit_animation_to_y_delta);
2780                 animator.setInterpolator(Interpolators.STANDARD_ACCELERATE);
2781                 animator.setDuration(resources.getInteger(R.integer.config_activityShortDur));
2782             }
2783 
2784             Window window = getWindow();
2785             int rotation = window.getWindowManager().getDefaultDisplay().getRotation();
2786 
2787             animator.addUpdateListener(valueAnimator -> {
2788                 float progress = (float) valueAnimator.getAnimatedValue();
2789 
2790                 float alpha = isEnter ? progress : 1 - progress;
2791                 mGlobalActionsLayout.setAlpha(alpha);
2792                 window.setDimAmount(mWindowDimAmount * alpha);
2793 
2794                 // TODO(b/213872558): Support devices that don't have their power button on the
2795                 // right.
2796                 float translation =
2797                         isEnter ? translationPx * (1 - progress) : translationPx * progress;
2798                 switch (rotation) {
2799                     case Surface.ROTATION_0:
2800                         mGlobalActionsLayout.setTranslationX(translation);
2801                         break;
2802                     case Surface.ROTATION_90:
2803                         mGlobalActionsLayout.setTranslationY(-translation);
2804                         break;
2805                     case Surface.ROTATION_180:
2806                         mGlobalActionsLayout.setTranslationX(-translation);
2807                         break;
2808                     case Surface.ROTATION_270:
2809                         mGlobalActionsLayout.setTranslationY(translation);
2810                         break;
2811                 }
2812             });
2813 
2814             animator.addListener(new AnimatorListenerAdapter() {
2815                 private int mPreviousLayerType;
2816 
2817                 @Override
2818                 public void onAnimationStart(Animator animation, boolean isReverse) {
2819                     mPreviousLayerType = mGlobalActionsLayout.getLayerType();
2820                     mGlobalActionsLayout.setLayerType(View.LAYER_TYPE_HARDWARE, null);
2821                 }
2822 
2823                 @Override
2824                 public void onAnimationEnd(Animator animation) {
2825                     mGlobalActionsLayout.setLayerType(mPreviousLayerType, null);
2826                     if (then != null) {
2827                         then.run();
2828                     }
2829                 }
2830             });
2831 
2832             animator.start();
2833         }
2834 
2835         @Override
2836         public void dismiss() {
2837             dismissOverflow();
2838             dismissPowerOptions();
2839 
2840             mNotificationShadeWindowController.setRequestTopUi(false, TAG);
2841             super.dismiss();
2842         }
2843 
2844         protected final void dismissOverflow() {
2845             if (mOverflowPopup != null) {
2846                 mOverflowPopup.dismiss();
2847             }
2848         }
2849 
2850         protected final void dismissPowerOptions() {
2851             if (mPowerOptionsDialog != null) {
2852                 mPowerOptionsDialog.dismiss();
2853             }
2854         }
2855 
2856         protected final void setRotationSuggestionsEnabled(boolean enabled) {
2857             try {
2858                 final int userId = Binder.getCallingUserHandle().getIdentifier();
2859                 final int what = enabled
2860                         ? StatusBarManager.DISABLE2_NONE
2861                         : StatusBarManager.DISABLE2_ROTATE_SUGGESTIONS;
2862                 mStatusBarService.disable2ForUser(what, mToken, mContext.getPackageName(), userId);
2863             } catch (RemoteException ex) {
2864                 throw ex.rethrowFromSystemServer();
2865             }
2866         }
2867 
2868         @Override
2869         public void onColorsChanged(ColorExtractor extractor, int which) {
2870             if (mKeyguardShowing) {
2871                 if ((WallpaperManager.FLAG_LOCK & which) != 0) {
2872                     updateColors(extractor.getColors(WallpaperManager.FLAG_LOCK),
2873                             true /* animate */);
2874                 }
2875             } else {
2876                 if ((WallpaperManager.FLAG_SYSTEM & which) != 0) {
2877                     updateColors(extractor.getColors(WallpaperManager.FLAG_SYSTEM),
2878                             true /* animate */);
2879                 }
2880             }
2881         }
2882 
2883         public void setKeyguardShowing(boolean keyguardShowing) {
2884             mKeyguardShowing = keyguardShowing;
2885         }
2886 
2887         public void refreshDialog() {
2888             mOnRefreshCallback.run();
2889 
2890             // Dismiss the dropdown menus.
2891             dismissOverflow();
2892             dismissPowerOptions();
2893 
2894             // Update the list as the max number of items per row has probably changed.
2895             mGlobalActionsLayout.updateList();
2896         }
2897 
2898         public void onRotate(int from, int to) {
2899             refreshDialog();
2900         }
2901     }
2902 }
2903