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