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