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