全网最详细的Android11.0长按power键关机流程分析

创作不易,请尊重原创,转载须注明出处:https://blog.csdn.net/An_Times/article/details/120027015

一、前言

本文将分析PhoneWindowMananger按键响应power键长按事件到SystemUI弹出关机界面dialog的流程。Android 原生的关机界面有两种,一种是LegacyGlobalActions中的dialog (第二张图),另一种是SystemUI中的GlobalActionsDialog(第一张图),常规情况下默认是弹出SystemUI中的关机界面,当SystemUI没有正常工作时才会弹出LegacyGlobalActions中的关机界面。

SystemUI关机界面

Framework关机界面

环境:本文基于MTK 6765 Android 11.0

二、PhoneWindowManager处理长按power键事件


  • 代码路径:frameworks\base\services\core\java\com\android\server\policy\PhoneWindowManager.java
  • 按键事件从input子系统传递上来首选是经过PhoneWindowManager处理的,像一些系统比如关机键、音量键等等是在interceptKeyBeforeQueueing中处理并不会继续往下分发。
  • power键按下的时候调用的是interceptPowerKeyDown方法。
    @Override
    public int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags) {
        ...此处省略部分代码
            case KeyEvent.KEYCODE_POWER: {
                EventLogTags.writeInterceptPower(
                        KeyEvent.actionToString(event.getAction()),
                        mPowerKeyHandled ? 1 : 0, mPowerKeyPressCounter);
                // Any activity on the power button stops the accessibility shortcut
                cancelPendingAccessibilityShortcutAction();
                result &= ~ACTION_PASS_TO_USER;
                isWakeKey = false; // wake-up will be handled separately
                if (down) {
                    interceptPowerKeyDown(event, interactive);
                } else {
                    interceptPowerKeyUp(event, interactive, canceled);
                }
                break;
            }
        ...此处省略部分代码
    }
  • 长按power键最终调用的是powerLongPress() 方法。
   private void powerLongPress() {
        final int behavior = getResolvedLongPressOnPowerBehavior();
        switch (behavior) {
            case LONG_PRESS_POWER_NOTHING:
                break;
            case LONG_PRESS_POWER_GLOBAL_ACTIONS:
                mPowerKeyHandled = true;
                performHapticFeedback(HapticFeedbackConstants.LONG_PRESS, false,
                        "Power - Long Press - Global Actions");
                showGlobalActionsInternal();
                break;
    ...此处省略部分代码
        }
    }
  • 长按power键弹框是调用showGlobalActionsInternal()方法。这个方法实际上调用的是GlobalActions 的showDialog 方法。
    void showGlobalActionsInternal() {
        if (mGlobalActions == null) {
            mGlobalActions = new GlobalActions(mContext, mWindowManagerFuncs);
        }
        final boolean keyguardShowing = isKeyguardShowingAndNotOccluded();
        mGlobalActions.showDialog(keyguardShowing, isDeviceProvisioned());
        // since it took two seconds of long press to bring this up,
        // poke the wake lock so they have some time to see the dialog.
        mPowerManager.userActivity(SystemClock.uptimeMillis(), false);
    }

三、弹出关机界面Dialog


  • 代码路径:frameworks\base\services\core\java\com\android\server\policy\GlobalActions.java

      } else {
    
  • showDialog方法中根据mGlobalActionsAvailable 变量来判断弹出哪一种关机界面dialog,即开头所说的,SystemUI弹的关机界面是mGlobalActionsProvider.showGlobalActions(),也就是下图这种,framework中原因的关机界面是mLegacyGlobalActions.showDialog。这里通过log打印mGlobalActionsAvailable的值为true,即说明正常用的是SystemUI中的关机界面。

SystemUI关机界面
    public void showDialog(boolean keyguardShowing, boolean deviceProvisioned) {
        if (DEBUG) Slog.d(TAG, "showDialog " + keyguardShowing + " " + deviceProvisioned);
        if (mGlobalActionsProvider != null && mGlobalActionsProvider.isGlobalActionsDisabled()) {
            return;
        }
        mKeyguardShowing = keyguardShowing;
        mDeviceProvisioned = deviceProvisioned;
        mShowing = true;
        if (mGlobalActionsAvailable) {
            mHandler.postDelayed(mShowTimeout, 5000);
            mGlobalActionsProvider.showGlobalActions();
        } else {
            // SysUI isn't alive, show legacy menu.
            ensureLegacyCreated();
            mLegacyGlobalActions.showDialog(mKeyguardShowing, mDeviceProvisioned);
        }
    }

三、GlobalActionsProvider 与SystemUI关联起来的过程


  • 代码路径:frameworks\base\services\core\java\com\android\server\policy\GlobalActionsProvider.java

  • GlobalActionsProvider是一个接口类,包含一个子接口类GlobalActionsListener,弹出关机界面主要是showGlobalActions 方法。其他地方是哪里调用了showGlobalActions呢,这里全局搜索一下GlobalActionsProvider即可。

package com.android.server.policy;

/** Used with LocalServices to add custom handling to global actions. */
public interface GlobalActionsProvider {
    /** @return {@code true} if the dialog is enabled. */
    boolean isGlobalActionsDisabled();
    /** Set the listener that will handle various global actions evetns. */
    void setGlobalActionsListener(GlobalActionsListener listener);
    /** Show the global actions UI to the user. */
    void showGlobalActions();

    /** Listener to pass global actions events back to system. */
    public interface GlobalActionsListener {
        /**
         * Called when sysui starts and connects its status bar, or when the status bar binder
         * dies indicating sysui is no longer alive.
         */
        void onGlobalActionsAvailableChanged(boolean available);

        /**
         * Callback from sysui to notify system that global actions has been successfully shown.
         */
        void onGlobalActionsShown();

        /**
         * Callback from sysui to notify system that the user has dismissed global actions and
         * it no longer needs to be displayed (even if sysui dies).
         */
        void onGlobalActionsDismissed();
    }
}

3.2、StatusBarManagerService 登场


  • 代码路径:frameworks\base\services\core\java\com\android\server\statusbar\StatusBarManagerService.java

  • 第2节中 mGlobalActionsProvider.showGlobalActions()调用的实际上是mBar.showGlobalActionsMenu(),这里mBar就是IStatusBar。StatusBarManagerService相当于是客户端,全局搜索一下谁继承了IStatusBar.Stub 就是服务端了。

  • CommandQueue extends IStatusBar.Stub,说明CommandQueue是服务端。

    private final GlobalActionsProvider mGlobalActionsProvider = new GlobalActionsProvider() {
        @Override
        public boolean isGlobalActionsDisabled() {
            // TODO(b/118592525): support global actions for multi-display.
            final int disabled2 = mDisplayUiState.get(DEFAULT_DISPLAY).getDisabled2();
            return (disabled2 & DISABLE2_GLOBAL_ACTIONS) != 0;
        }

        @Override
        public void setGlobalActionsListener(GlobalActionsProvider.GlobalActionsListener listener) {
            mGlobalActionListener = listener;
            mGlobalActionListener.onGlobalActionsAvailableChanged(mBar != null);
        }

        @Override
        public void showGlobalActions() {
            if (mBar != null) {
                try {
                    mBar.showGlobalActionsMenu();
                } catch (RemoteException ex) {}
            }
        }
    };

3.3、CommandQueue的 showGlobalActionsMenu 方法


  • 上文中提到mGlobalActionsProvider.showGlobalActions() 调用的实际上是CommandQueue 的showGlobalActionsMenu。
  • 紧接着又调用了mCallbacks.get(i).handleShowGlobalActionsMenu(),这个Callbacks 是CommandQueue内部的一个接口类。我们看谁实现了这个Callbacks 接口就知道下一步调用的哪里了。
  • 使用命令“grep -rn “implements Callbacks” vendor/mediatek/proprietary/packages/apps/SystemUI/” 最终查到是GlobalActionsComponent.java,那么下一步就看GlobalActionsComponent 的handleShowGlobalActionsMenu()方法就好了。
    @Override
    public void showGlobalActionsMenu() {
        synchronized (mLock) {
            mHandler.removeMessages(MSG_SHOW_GLOBAL_ACTIONS);
            mHandler.obtainMessage(MSG_SHOW_GLOBAL_ACTIONS).sendToTarget();
        }
    }


    ...此处省略部分代码

                    case MSG_SHOW_GLOBAL_ACTIONS:
                    for (int i = 0; i < mCallbacks.size(); i++) {
                        mCallbacks.get(i).handleShowGlobalActionsMenu();
                    }

3.4、GlobalActionsComponent 的handleShowGlobalActionsMenu()方法


  • 看下mExtension定义:private Extension mExtension,那么实际是调用GlobalActions的showGlobalActions。
    @Override
    public void handleShowGlobalActionsMenu() {
        mStatusBarKeyguardViewManager.setGlobalActionsVisible(true);
        mExtension.get().showGlobalActions(this);
    }

3.5、SystemUI的GlobalActions


  • 代码路径:vendor\mediatek\proprietary\packages\apps\SystemUI\plugin\src\com\android\systemui\plugins\GlobalActions.java

  • SystemUI的GlobalActions 跟framework中的GlobalActions 差不多,都是一个接口类,这里绕了一大圈还是调用同名的GlobalActions,这个过程确实很繁琐,可能是为了适配SystemUI的架构这样搞的。

  • “grep -rn “showGlobalActions” vendor/mediatek/proprietary/packages/apps/SystemUI/” 可以搜索到GlobalActionsImpl 中调用了showGlobalActions。

package com.android.systemui.plugins;

import com.android.systemui.plugins.GlobalActions.GlobalActionsManager;
import com.android.systemui.plugins.annotations.DependsOn;
import com.android.systemui.plugins.annotations.ProvidesInterface;

@ProvidesInterface(action = GlobalActions.ACTION, version = GlobalActions.VERSION)
@DependsOn(target = GlobalActionsManager.class)
public interface GlobalActions extends Plugin {

    String ACTION = "com.android.systemui.action.PLUGIN_GLOBAL_ACTIONS";
    int VERSION = 1;

    void showGlobalActions(GlobalActionsManager manager);
    default void showShutdownUi(boolean isReboot, String reason) {
    }

    default void destroy() {
    }

    @ProvidesInterface(version = GlobalActionsManager.VERSION)
    public interface GlobalActionsManager {
        int VERSION = 1;

        void onGlobalActionsShown();
        void onGlobalActionsHidden();

        void shutdown();
        void reboot(boolean safeMode);
    }
}

3.6、GlobalActionsImpl 调用showGlobalActions


  • 代码路径:vendor\mediatek\proprietary\packages\apps\SystemUI\src\com\android\systemui\globalactions\GlobalActionsImpl.java
  • GlobalActionsImpl 的showGlobalActions实际上是mGlobalActionsDialog.showOrHideDialog,这里mGlobalActionsDialog 就是mGlobalActionsDialog,终于找到最终调用关机界面的地方了。
    @Override
    public void showGlobalActions(GlobalActionsManager manager) {
        if (mDisabled) return;
        mGlobalActionsDialog = mGlobalActionsDialogLazy.get();
        mGlobalActionsDialog.showOrHideDialog(mKeyguardStateController.isShowing(),
                mDeviceProvisionedController.isDeviceProvisioned(),
                mWalletPluginProvider.get());
        Dependency.get(KeyguardUpdateMonitor.class).requestFaceAuth();
    }

四、最终的关机界面 GlobalActionsDialog


  • 代码路径:vendor\mediatek\proprietary\packages\apps\SystemUI\src\com\android\systemui\globalactions\GlobalActionsDialog.java

  • GlobalActionsDialog中有几个重要的方法,createDialog创建关机对话框;

  • 关机界面有很多功能 例如关机、重启、静音都是像“ShutDownAction extends SinglePressAction implements LongPressAction” 这样形式实现的内部类。并且个功能单按点击是onPress() 方法,长按点击是
    onLongPress() 方法。

/*
 * Copyright (C) 2017 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the
 * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied. See the License for the specific language governing
 * permissions and limitations under the License.
 */

package com.android.systemui.globalactions;

import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_GLOBAL_ACTIONS;
import static android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN;

import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_GLOBAL_ACTIONS_SHOWING;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.Dialog;
import android.app.IActivityManager;
import android.app.PendingIntent;
import android.app.StatusBarManager;
import android.app.WallpaperManager;
import android.app.admin.DevicePolicyManager;
import android.app.trust.TrustManager;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.pm.UserInfo;
import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.database.ContentObserver;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.media.AudioManager;
import android.net.ConnectivityManager;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.RemoteException;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
import android.os.Vibrator;
import android.provider.Settings;
import android.service.dreams.IDreamManager;
import android.sysprop.TelephonyProperties;
import android.telecom.TelecomManager;
import android.telephony.PhoneStateListener;
import android.telephony.ServiceState;
import android.telephony.TelephonyManager;
import android.transition.AutoTransition;
import android.transition.TransitionManager;
import android.transition.TransitionSet;
import android.util.ArraySet;
import android.util.FeatureFlagUtils;
import android.util.Log;
import android.view.ContextThemeWrapper;
import android.view.IWindowManager;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowInsets;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityEvent;
import android.widget.BaseAdapter;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.ImageView.ScaleType;
import android.widget.LinearLayout;
import android.widget.ListPopupWindow;
import android.widget.TextView;

import androidx.annotation.NonNull;
import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.LifecycleRegistry;

import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.colorextraction.ColorExtractor;
import com.android.internal.colorextraction.ColorExtractor.GradientColors;
import com.android.internal.colorextraction.drawable.ScrimDrawable;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.UiEvent;
import com.android.internal.logging.UiEventLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.util.EmergencyAffordanceManager;
import com.android.internal.util.ScreenRecordHelper;
import com.android.internal.util.ScreenshotHelper;
import com.android.internal.view.RotationPolicy;
import com.android.internal.widget.LockPatternUtils;
import com.android.systemui.Interpolators;
import com.android.systemui.MultiListLayout;
import com.android.systemui.MultiListLayout.MultiListAdapter;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.controls.ControlsServiceInfo;
import com.android.systemui.controls.controller.ControlsController;
import com.android.systemui.controls.dagger.ControlsComponent;
import com.android.systemui.controls.management.ControlsAnimations;
import com.android.systemui.controls.ui.ControlsUiController;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.model.SysUiState;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.GlobalActions.GlobalActionsManager;
import com.android.systemui.plugins.GlobalActionsPanelPlugin;
import com.android.systemui.settings.CurrentUserContextTracker;
import com.android.systemui.statusbar.NotificationShadeDepthController;
import com.android.systemui.statusbar.phone.NotificationShadeWindowController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.EmergencyDialerConstants;
import com.android.systemui.util.RingerModeTracker;
import com.android.systemui.util.leak.RotationUtils;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.Executor;

import javax.inject.Inject;

/**
 * Helper to show the global actions dialog.  Each item is an {@link Action} that may show depending
 * on whether the keyguard is showing, and whether the device is provisioned.
 */
public class GlobalActionsDialog implements DialogInterface.OnDismissListener,
        DialogInterface.OnShowListener,
        ConfigurationController.ConfigurationListener,
        GlobalActionsPanelPlugin.Callbacks,
        LifecycleOwner {

    public static final String SYSTEM_DIALOG_REASON_KEY = "reason";
    public static final String SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS = "globalactions";
    public static final String SYSTEM_DIALOG_REASON_DREAM = "dream";

    private static final String TAG = "GlobalActionsDialog";

    private static final boolean SHOW_SILENT_TOGGLE = true;

    /* Valid settings for global actions keys.
     * see config.xml config_globalActionList */
    @VisibleForTesting
    static final String GLOBAL_ACTION_KEY_POWER = "power";
    private static final String GLOBAL_ACTION_KEY_AIRPLANE = "airplane";
    static final String GLOBAL_ACTION_KEY_BUGREPORT = "bugreport";
    private static final String GLOBAL_ACTION_KEY_SILENT = "silent";
    private static final String GLOBAL_ACTION_KEY_USERS = "users";
    private static final String GLOBAL_ACTION_KEY_SETTINGS = "settings";
    static final String GLOBAL_ACTION_KEY_LOCKDOWN = "lockdown";
    private static final String GLOBAL_ACTION_KEY_VOICEASSIST = "voiceassist";
    private static final String GLOBAL_ACTION_KEY_ASSIST = "assist";
    static final String GLOBAL_ACTION_KEY_RESTART = "restart";
    private static final String GLOBAL_ACTION_KEY_LOGOUT = "logout";
    static final String GLOBAL_ACTION_KEY_EMERGENCY = "emergency";
    static final String GLOBAL_ACTION_KEY_SCREENSHOT = "screenshot";

    public static final String PREFS_CONTROLS_SEEDING_COMPLETED = "SeedingCompleted";
    public static final String PREFS_CONTROLS_FILE = "controls_prefs";
    private static final int SEEDING_MAX = 2;

    private final Context mContext;
    private final GlobalActionsManager mWindowManagerFuncs;
    private final AudioManager mAudioManager;
    private final IDreamManager mDreamManager;
    private final DevicePolicyManager mDevicePolicyManager;
    private final LockPatternUtils mLockPatternUtils;
    private final KeyguardStateController mKeyguardStateController;
    private final BroadcastDispatcher mBroadcastDispatcher;
    private final ContentResolver mContentResolver;
    private final Resources mResources;
    private final ConfigurationController mConfigurationController;
    private final UserManager mUserManager;
    private final TrustManager mTrustManager;
    private final IActivityManager mIActivityManager;
    private final TelecomManager mTelecomManager;
    private final MetricsLogger mMetricsLogger;
    private final UiEventLogger mUiEventLogger;
    private final NotificationShadeDepthController mDepthController;
    private final SysUiState mSysUiState;

    // Used for RingerModeTracker
    private final LifecycleRegistry mLifecycle = new LifecycleRegistry(this);

    @VisibleForTesting
    protected final ArrayList<Action> mItems = new ArrayList<>();
    @VisibleForTesting
    protected final ArrayList<Action> mOverflowItems = new ArrayList<>();
    @VisibleForTesting
    protected final ArrayList<Action> mPowerItems = new ArrayList<>();

    @VisibleForTesting
    protected ActionsDialog mDialog;

    private Action mSilentModeAction;
    private ToggleAction mAirplaneModeOn;

    private MyAdapter mAdapter;
    private MyOverflowAdapter mOverflowAdapter;
    private MyPowerOptionsAdapter mPowerAdapter;

    private boolean mKeyguardShowing = false;
    private boolean mDeviceProvisioned = false;
    private ToggleState mAirplaneState = ToggleState.Off;
    private boolean mIsWaitingForEcmExit = false;
    private boolean mHasTelephony;
    private boolean mHasVibrator;
    private final boolean mShowSilentToggle;
    private final EmergencyAffordanceManager mEmergencyAffordanceManager;
    private final ScreenshotHelper mScreenshotHelper;
    private final ScreenRecordHelper mScreenRecordHelper;
    private final ActivityStarter mActivityStarter;
    private final SysuiColorExtractor mSysuiColorExtractor;
    private final IStatusBarService mStatusBarService;
    private final NotificationShadeWindowController mNotificationShadeWindowController;
    private GlobalActionsPanelPlugin mWalletPlugin;
    private Optional<ControlsUiController> mControlsUiControllerOptional;
    private final IWindowManager mIWindowManager;
    private final Executor mBackgroundExecutor;
    private List<ControlsServiceInfo> mControlsServiceInfos = new ArrayList<>();
    private Optional<ControlsController> mControlsControllerOptional;
    private final RingerModeTracker mRingerModeTracker;
    private int mDialogPressDelay = DIALOG_PRESS_DELAY; // ms
    private Handler mMainHandler;
    private CurrentUserContextTracker mCurrentUserContextTracker;
    @VisibleForTesting
    boolean mShowLockScreenCardsAndControls = false;

    @VisibleForTesting
    public enum GlobalActionsEvent implements UiEventLogger.UiEventEnum {
        @UiEvent(doc = "The global actions / power menu surface became visible on the screen.")
        GA_POWER_MENU_OPEN(337),

        @UiEvent(doc = "The global actions / power menu surface was dismissed.")
        GA_POWER_MENU_CLOSE(471),

        @UiEvent(doc = "The global actions bugreport button was pressed.")
        GA_BUGREPORT_PRESS(344),

        @UiEvent(doc = "The global actions bugreport button was long pressed.")
        GA_BUGREPORT_LONG_PRESS(345),

        @UiEvent(doc = "The global actions emergency button was pressed.")
        GA_EMERGENCY_DIALER_PRESS(346),

        @UiEvent(doc = "The global actions screenshot button was pressed.")
        GA_SCREENSHOT_PRESS(347),

        @UiEvent(doc = "The global actions screenshot button was long pressed.")
        GA_SCREENSHOT_LONG_PRESS(348);

        private final int mId;

        GlobalActionsEvent(int id) {
            mId = id;
        }

        @Override
        public int getId() {
            return mId;
        }
    }

    /**
     * @param context everything needs a context :(
     */
    @Inject
    public GlobalActionsDialog(Context context, GlobalActionsManager windowManagerFuncs,
            AudioManager audioManager, IDreamManager iDreamManager,
            DevicePolicyManager devicePolicyManager, LockPatternUtils lockPatternUtils,
            BroadcastDispatcher broadcastDispatcher,
            ConnectivityManager connectivityManager, TelephonyManager telephonyManager,
            ContentResolver contentResolver, @Nullable Vibrator vibrator, @Main Resources resources,
            ConfigurationController configurationController, ActivityStarter activityStarter,
            KeyguardStateController keyguardStateController, UserManager userManager,
            TrustManager trustManager, IActivityManager iActivityManager,
            @Nullable TelecomManager telecomManager, MetricsLogger metricsLogger,
            NotificationShadeDepthController depthController, SysuiColorExtractor colorExtractor,
            IStatusBarService statusBarService,
            NotificationShadeWindowController notificationShadeWindowController,
            IWindowManager iWindowManager,
            @Background Executor backgroundExecutor,
            UiEventLogger uiEventLogger,
            RingerModeTracker ringerModeTracker, SysUiState sysUiState, @Main Handler handler,
            ControlsComponent controlsComponent,
            CurrentUserContextTracker currentUserContextTracker) {
        mContext = context;
        mWindowManagerFuncs = windowManagerFuncs;
        mAudioManager = audioManager;
        mDreamManager = iDreamManager;
        mDevicePolicyManager = devicePolicyManager;
        mLockPatternUtils = lockPatternUtils;
        mKeyguardStateController = keyguardStateController;
        mBroadcastDispatcher = broadcastDispatcher;
        mContentResolver = contentResolver;
        mResources = resources;
        mConfigurationController = configurationController;
        mUserManager = userManager;
        mTrustManager = trustManager;
        mIActivityManager = iActivityManager;
        mTelecomManager = telecomManager;
        mMetricsLogger = metricsLogger;
        mUiEventLogger = uiEventLogger;
        mDepthController = depthController;
        mSysuiColorExtractor = colorExtractor;
        mStatusBarService = statusBarService;
        mNotificationShadeWindowController = notificationShadeWindowController;
        mControlsUiControllerOptional = controlsComponent.getControlsUiController();
        mIWindowManager = iWindowManager;
        mBackgroundExecutor = backgroundExecutor;
        mRingerModeTracker = ringerModeTracker;
        mControlsControllerOptional = controlsComponent.getControlsController();
        mSysUiState = sysUiState;
        mMainHandler = handler;
        mCurrentUserContextTracker = currentUserContextTracker;

        // receive broadcasts
        IntentFilter filter = new IntentFilter();
        filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
        filter.addAction(Intent.ACTION_SCREEN_OFF);
        filter.addAction(TelephonyManager.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED);
        mBroadcastDispatcher.registerReceiver(mBroadcastReceiver, filter);

        mHasTelephony = connectivityManager.isNetworkSupported(ConnectivityManager.TYPE_MOBILE);

        // get notified of phone state changes
        telephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_SERVICE_STATE);
        contentResolver.registerContentObserver(
                Settings.Global.getUriFor(Settings.Global.AIRPLANE_MODE_ON), true,
                mAirplaneModeObserver);
        mHasVibrator = vibrator != null && vibrator.hasVibrator();

        mShowSilentToggle = SHOW_SILENT_TOGGLE && !resources.getBoolean(
                R.bool.config_useFixedVolume);
        if (mShowSilentToggle) {
            mRingerModeTracker.getRingerMode().observe(this, ringer ->
                    mHandler.sendEmptyMessage(MESSAGE_REFRESH)
            );
        }

        mEmergencyAffordanceManager = new EmergencyAffordanceManager(context);
        mScreenshotHelper = new ScreenshotHelper(context);
        mScreenRecordHelper = new ScreenRecordHelper(context);

        mConfigurationController.addCallback(this);

        mActivityStarter = activityStarter;
        keyguardStateController.addCallback(new KeyguardStateController.Callback() {
            @Override
            public void onUnlockedChanged() {
                if (mDialog != null) {
                    boolean unlocked = mKeyguardStateController.isUnlocked();
                    if (mDialog.mWalletViewController != null) {
                        mDialog.mWalletViewController.onDeviceLockStateChanged(!unlocked);
                    }
                    if (!mDialog.isShowingControls() && shouldShowControls()) {
                        mDialog.showControls(mControlsUiControllerOptional.get());
                    }
                    if (unlocked) {
                        mDialog.hideLockMessage();
                    }
                }
            }
        });

        if (controlsComponent.getControlsListingController().isPresent()) {
            controlsComponent.getControlsListingController().get()
                    .addCallback(list -> {
                        mControlsServiceInfos = list;
                        // This callback may occur after the dialog has been shown. If so, add
                        // controls into the already visible space or show the lock msg if needed.
                        if (mDialog != null) {
                            if (!mDialog.isShowingControls() && shouldShowControls()) {
                                mDialog.showControls(mControlsUiControllerOptional.get());
                            } else if (shouldShowLockMessage()) {
                                mDialog.showLockMessage();
                            }
                        }
                    });
        }

        // Listen for changes to show controls on the power menu while locked
        onPowerMenuLockScreenSettingsChanged();
        mContext.getContentResolver().registerContentObserver(
                Settings.Secure.getUriFor(Settings.Secure.POWER_MENU_LOCKED_SHOW_CONTENT),
                false /* notifyForDescendants */,
                new ContentObserver(mMainHandler) {
                    @Override
                    public void onChange(boolean selfChange) {
                        onPowerMenuLockScreenSettingsChanged();
                    }
                });
    }

    /**
     * See if any available control service providers match one of the preferred components. If
     * they do, and there are no current favorites for that component, query the preferred
     * component for a limited number of suggested controls.
     */
    private void seedFavorites() {
        if (!mControlsControllerOptional.isPresent()
                || mControlsServiceInfos.isEmpty()) {
            return;
        }

        String[] preferredControlsPackages = mContext.getResources()
                .getStringArray(com.android.systemui.R.array.config_controlsPreferredPackages);

        SharedPreferences prefs = mCurrentUserContextTracker.getCurrentUserContext()
                .getSharedPreferences(PREFS_CONTROLS_FILE, Context.MODE_PRIVATE);
        Set<String> seededPackages = prefs.getStringSet(PREFS_CONTROLS_SEEDING_COMPLETED,
                Collections.emptySet());

        List<ComponentName> componentsToSeed = new ArrayList<>();
        for (int i = 0; i < Math.min(SEEDING_MAX, preferredControlsPackages.length); i++) {
            String pkg = preferredControlsPackages[i];
            for (ControlsServiceInfo info : mControlsServiceInfos) {
                if (!pkg.equals(info.componentName.getPackageName())) continue;
                if (seededPackages.contains(pkg)) {
                    break;
                } else if (mControlsControllerOptional.get()
                        .countFavoritesForComponent(info.componentName) > 0) {
                    // When there are existing controls but no saved preference, assume it
                    // is out of sync, perhaps through a device restore, and update the
                    // preference
                    addPackageToSeededSet(prefs, pkg);
                    break;
                }
                componentsToSeed.add(info.componentName);
                break;
            }
        }

        if (componentsToSeed.isEmpty()) return;

        mControlsControllerOptional.get().seedFavoritesForComponents(
                componentsToSeed,
                (response) -> {
                    Log.d(TAG, "Controls seeded: " + response);
                    if (response.getAccepted()) {
                        addPackageToSeededSet(prefs, response.getPackageName());
                    }
                });
    }

    private void addPackageToSeededSet(SharedPreferences prefs, String pkg) {
        Set<String> seededPackages = prefs.getStringSet(PREFS_CONTROLS_SEEDING_COMPLETED,
                Collections.emptySet());
        Set<String> updatedPkgs = new HashSet<>(seededPackages);
        updatedPkgs.add(pkg);
        prefs.edit().putStringSet(PREFS_CONTROLS_SEEDING_COMPLETED, updatedPkgs).apply();
    }

    /**
     * Show the global actions dialog (creating if necessary)
     *
     * @param keyguardShowing True if keyguard is showing
     */
    public void showOrHideDialog(boolean keyguardShowing, boolean isDeviceProvisioned,
            GlobalActionsPanelPlugin walletPlugin) {
        mKeyguardShowing = keyguardShowing;
        mDeviceProvisioned = isDeviceProvisioned;
        mWalletPlugin = walletPlugin;
        if (mDialog != null && mDialog.isShowing()) {
            // In order to force global actions to hide on the same affordance press, we must
            // register a call to onGlobalActionsShown() first to prevent the default actions
            // menu from showing. This will be followed by a subsequent call to
            // onGlobalActionsHidden() on dismiss()
            mWindowManagerFuncs.onGlobalActionsShown();
            mDialog.dismiss();
            mDialog = null;
        } else {
            handleShow();
        }
    }

    /**
     * Dismiss the global actions dialog, if it's currently shown
     */
    public void dismissDialog() {
        mHandler.removeMessages(MESSAGE_DISMISS);
        mHandler.sendEmptyMessage(MESSAGE_DISMISS);
    }

    private void awakenIfNecessary() {
        if (mDreamManager != null) {
            try {
                if (mDreamManager.isDreaming()) {
                    mDreamManager.awaken();
                }
            } catch (RemoteException e) {
                // we tried
            }
        }
    }

    private void handleShow() {
        awakenIfNecessary();
        mDialog = createDialog();
        prepareDialog();
        seedFavorites();

        WindowManager.LayoutParams attrs = mDialog.getWindow().getAttributes();
        attrs.setTitle("ActionsDialog");
        attrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
        mDialog.getWindow().setAttributes(attrs);
        // Don't acquire soft keyboard focus, to avoid destroying state when capturing bugreports
        mDialog.getWindow().setFlags(FLAG_ALT_FOCUSABLE_IM, FLAG_ALT_FOCUSABLE_IM);
        mDialog.show();
        mWindowManagerFuncs.onGlobalActionsShown();
    }

    @VisibleForTesting
    protected boolean shouldShowAction(Action action) {
        if (mKeyguardShowing && !action.showDuringKeyguard()) {
            return false;
        }
        if (!mDeviceProvisioned && !action.showBeforeProvisioning()) {
            return false;
        }
        return true;
    }

    /**
     * Returns the maximum number of power menu items to show based on which GlobalActions
     * layout is being used.
     */
    @VisibleForTesting
    protected int getMaxShownPowerItems() {
        return mResources.getInteger(com.android.systemui.R.integer.power_menu_max_columns);
    }

    /**
     * Add a power menu action item for to either the main or overflow items lists, depending on
     * whether controls are enabled and whether the max number of shown items has been reached.
     */
    private void addActionItem(Action action) {
        if (mItems.size() < getMaxShownPowerItems()) {
            mItems.add(action);
        } else {
            mOverflowItems.add(action);
        }
    }

    @VisibleForTesting
    protected String[] getDefaultActions() {
        return mResources.getStringArray(R.array.config_globalActionsList);
    }

    private void addIfShouldShowAction(List<Action> actions, Action action) {
        if (shouldShowAction(action)) {
            actions.add(action);
        }
    }

    @VisibleForTesting
    protected void createActionItems() {
        // Simple toggle style if there's no vibrator, otherwise use a tri-state
        if (!mHasVibrator) {
            mSilentModeAction = new SilentModeToggleAction();
        } else {
            mSilentModeAction = new SilentModeTriStateAction(mAudioManager, mHandler);
        }
        mAirplaneModeOn = new AirplaneModeAction();
        onAirplaneModeChanged();

        mItems.clear();
        mOverflowItems.clear();
        mPowerItems.clear();
        String[] defaultActions = getDefaultActions();

        ShutDownAction shutdownAction = new ShutDownAction();
        RestartAction restartAction = new RestartAction();
        ArraySet<String> addedKeys = new ArraySet<String>();
        List<Action> tempActions = new ArrayList<>();
        CurrentUserProvider currentUser = new CurrentUserProvider();

        // make sure emergency affordance action is first, if needed
        if (mEmergencyAffordanceManager.needsEmergencyAffordance()) {
            addIfShouldShowAction(tempActions, new EmergencyAffordanceAction());
            addedKeys.add(GLOBAL_ACTION_KEY_EMERGENCY);
        }

        for (int i = 0; i < defaultActions.length; i++) {
            String actionKey = defaultActions[i];
            if (addedKeys.contains(actionKey)) {
                // If we already have added this, don't add it again.
                continue;
            }
            if (GLOBAL_ACTION_KEY_POWER.equals(actionKey)) {
                addIfShouldShowAction(tempActions, shutdownAction);
            } else if (GLOBAL_ACTION_KEY_AIRPLANE.equals(actionKey)) {
                addIfShouldShowAction(tempActions, mAirplaneModeOn);
            } else if (GLOBAL_ACTION_KEY_BUGREPORT.equals(actionKey)) {
                if (shouldDisplayBugReport(currentUser.get())) {
                    addIfShouldShowAction(tempActions, new BugReportAction());
                }
            } else if (GLOBAL_ACTION_KEY_SILENT.equals(actionKey)) {
                if (mShowSilentToggle) {
                    addIfShouldShowAction(tempActions, mSilentModeAction);
                }
            } else if (GLOBAL_ACTION_KEY_USERS.equals(actionKey)) {
                if (SystemProperties.getBoolean("fw.power_user_switcher", false)) {
                    addUserActions(tempActions, currentUser.get());
                }
            } else if (GLOBAL_ACTION_KEY_SETTINGS.equals(actionKey)) {
                addIfShouldShowAction(tempActions, getSettingsAction());
            } else if (GLOBAL_ACTION_KEY_LOCKDOWN.equals(actionKey)) {
                if (shouldDisplayLockdown(currentUser.get())) {
                    addIfShouldShowAction(tempActions, new LockDownAction());
                }
            } else if (GLOBAL_ACTION_KEY_VOICEASSIST.equals(actionKey)) {
                addIfShouldShowAction(tempActions, getVoiceAssistAction());
            } else if (GLOBAL_ACTION_KEY_ASSIST.equals(actionKey)) {
                addIfShouldShowAction(tempActions, getAssistAction());
            } else if (GLOBAL_ACTION_KEY_RESTART.equals(actionKey)) {
                addIfShouldShowAction(tempActions, restartAction);
            } else if (GLOBAL_ACTION_KEY_SCREENSHOT.equals(actionKey)) {
                addIfShouldShowAction(tempActions, new ScreenshotAction());
            } else if (GLOBAL_ACTION_KEY_LOGOUT.equals(actionKey)) {
                if (mDevicePolicyManager.isLogoutEnabled()
                        && currentUser.get() != null
                        && currentUser.get().id != UserHandle.USER_SYSTEM) {
                    addIfShouldShowAction(tempActions, new LogoutAction());
                }
            } else if (GLOBAL_ACTION_KEY_EMERGENCY.equals(actionKey)) {
                addIfShouldShowAction(tempActions, new EmergencyDialerAction());
            } else {
                Log.e(TAG, "Invalid global action key " + actionKey);
            }
            // Add here so we don't add more than one.
            addedKeys.add(actionKey);
        }

        // replace power and restart with a single power options action, if needed
        if (tempActions.contains(shutdownAction) && tempActions.contains(restartAction)
                && tempActions.size() > getMaxShownPowerItems()) {
            // transfer shutdown and restart to their own list of power actions
            int powerOptionsIndex = Math.min(tempActions.indexOf(restartAction),
                    tempActions.indexOf(shutdownAction));
            tempActions.remove(shutdownAction);
            tempActions.remove(restartAction);
            mPowerItems.add(shutdownAction);
            mPowerItems.add(restartAction);

            // add the PowerOptionsAction after Emergency, if present
            tempActions.add(powerOptionsIndex, new PowerOptionsAction());
        }
        for (Action action : tempActions) {
            addActionItem(action);
        }
    }

    private void onRotate() {
        // re-allocate actions between main and overflow lists
        this.createActionItems();
    }

    /**
     * Create the global actions dialog.
     *
     * @return A new dialog.
     */
    private ActionsDialog createDialog() {
        Log.d(TAG,"createDialog");
        createActionItems();

        mAdapter = new MyAdapter();
        mOverflowAdapter = new MyOverflowAdapter();
        mPowerAdapter = new MyPowerOptionsAdapter();

        mDepthController.setShowingHomeControls(true);
        GlobalActionsPanelPlugin.PanelViewController walletViewController =
                getWalletViewController();
        ControlsUiController uiController = null;
        if (mControlsUiControllerOptional.isPresent() && shouldShowControls()) {
            uiController = mControlsUiControllerOptional.get();
        }
        ActionsDialog dialog = new ActionsDialog(mContext, mAdapter, mOverflowAdapter,
                walletViewController, mDepthController, mSysuiColorExtractor,
                mStatusBarService, mNotificationShadeWindowController,
                controlsAvailable(), uiController,
                mSysUiState, this::onRotate, mKeyguardShowing, mPowerAdapter);

        if (shouldShowLockMessage()) {
            dialog.showLockMessage();
        }
        dialog.setCanceledOnTouchOutside(false); // Handled by the custom class.
        dialog.setOnDismissListener(this);
        dialog.setOnShowListener(this);

        return dialog;
    }

    @VisibleForTesting
    boolean shouldDisplayLockdown(UserInfo user) {
        if (user == null) {
            return false;
        }

        int userId = user.id;

        // No lockdown option if it's not turned on in Settings
        if (Settings.Secure.getIntForUser(mContentResolver,
                Settings.Secure.LOCKDOWN_IN_POWER_MENU, 0, userId) == 0) {
            return false;
        }

        // Lockdown is meaningless without a place to go.
        if (!mKeyguardStateController.isMethodSecure()) {
            return false;
        }

        // Only show the lockdown button if the device isn't locked down (for whatever reason).
        int state = mLockPatternUtils.getStrongAuthForUser(userId);
        return (state == STRONG_AUTH_NOT_REQUIRED
                || state == SOME_AUTH_REQUIRED_AFTER_USER_REQUEST);
    }

    @VisibleForTesting
    boolean shouldDisplayBugReport(UserInfo currentUser) {
        return Settings.Global.getInt(
                mContentResolver, Settings.Global.BUGREPORT_IN_POWER_MENU, 0) != 0
                && (currentUser == null || currentUser.isPrimary());
    }

    @Override
    public void onUiModeChanged() {
        mContext.getTheme().applyStyle(mContext.getThemeResId(), true);
        if (mDialog != null && mDialog.isShowing()) {
            mDialog.refreshDialog();
        }
    }

    public void destroy() {
        mConfigurationController.removeCallback(this);
    }

    @Nullable
    private GlobalActionsPanelPlugin.PanelViewController getWalletViewController() {
        if (mWalletPlugin == null) {
            return null;
        }
        return mWalletPlugin.onPanelShown(this, !mKeyguardStateController.isUnlocked());
    }

    /**
     * Implements {@link GlobalActionsPanelPlugin.Callbacks#dismissGlobalActionsMenu()}, which is
     * called when the quick access wallet requests dismissal.
     */
    @Override
    public void dismissGlobalActionsMenu() {
        dismissDialog();
    }

    /**
     * Implements {@link GlobalActionsPanelPlugin.Callbacks#dismissGlobalActionsMenu()}, which is
     * called when the quick access wallet requests that an intent be started (with lock screen
     * shown first if needed).
     */
    @Override
    public void startPendingIntentDismissingKeyguard(PendingIntent pendingIntent) {
        mActivityStarter.startPendingIntentDismissingKeyguard(pendingIntent);
    }

    @VisibleForTesting
    protected final class PowerOptionsAction extends SinglePressAction {
        private PowerOptionsAction() {
            super(com.android.systemui.R.drawable.ic_settings_power,
                    R.string.global_action_power_options);
        }

        @Override
        public boolean showDuringKeyguard() {
            return true;
        }

        @Override
        public boolean showBeforeProvisioning() {
            return true;
        }

        @Override
        public void onPress() {
            if (mDialog != null) {
                mDialog.showPowerOptionsMenu();
            }
        }
    }

    @VisibleForTesting
    final class ShutDownAction extends SinglePressAction implements LongPressAction {
        private ShutDownAction() {
            super(R.drawable.ic_lock_power_off,
                    R.string.global_action_power_off);
        }

        @Override
        public boolean onLongPress() {
            Log.d(TAG,"ShutDownAction::onLongPress()");
            if (!mUserManager.hasUserRestriction(UserManager.DISALLOW_SAFE_BOOT)) {
                mWindowManagerFuncs.reboot(true);
                return true;
            }
            return false;
        }

        @Override
        public boolean showDuringKeyguard() {
            return true;
        }

        @Override
        public boolean showBeforeProvisioning() {
            return true;
        }

        @Override
        public void onPress() {
            Log.d(TAG,"ShutDownAction::onPress()");
            // shutdown by making sure radio and power are handled accordingly.
            mWindowManagerFuncs.shutdown();
        }
    }

    @VisibleForTesting
    protected abstract class EmergencyAction extends SinglePressAction {
        EmergencyAction(int iconResId, int messageResId) {
            super(iconResId, messageResId);
        }

        @Override
        public boolean shouldBeSeparated() {
            return false;
        }

        @Override
        public View create(
                Context context, View convertView, ViewGroup parent, LayoutInflater inflater) {
            View v = super.create(context, convertView, parent, inflater);
            int textColor;
            v.setBackgroundTintList(ColorStateList.valueOf(v.getResources().getColor(
                    com.android.systemui.R.color.global_actions_emergency_background)));
            textColor = v.getResources().getColor(
                    com.android.systemui.R.color.global_actions_emergency_text);
            TextView messageView = v.findViewById(R.id.message);
            messageView.setTextColor(textColor);
            messageView.setSelected(true); // necessary for marquee to work
            ImageView icon = v.findViewById(R.id.icon);
            icon.getDrawable().setTint(textColor);
            return v;
        }

        @Override
        public boolean showDuringKeyguard() {
            return true;
        }

        @Override
        public boolean showBeforeProvisioning() {
            return true;
        }
    }

    private class EmergencyAffordanceAction extends EmergencyAction {
        EmergencyAffordanceAction() {
            super(R.drawable.emergency_icon,
                    R.string.global_action_emergency);
        }

        @Override
        public void onPress() {
            mEmergencyAffordanceManager.performEmergencyCall();
        }
    }

    @VisibleForTesting
    class EmergencyDialerAction extends EmergencyAction {
        private EmergencyDialerAction() {
            super(com.android.systemui.R.drawable.ic_emergency_star,
                    R.string.global_action_emergency);
        }

        @Override
        public void onPress() {
            mMetricsLogger.action(MetricsEvent.ACTION_EMERGENCY_DIALER_FROM_POWER_MENU);
            mUiEventLogger.log(GlobalActionsEvent.GA_EMERGENCY_DIALER_PRESS);
            if (mTelecomManager != null) {
                Intent intent = mTelecomManager.createLaunchEmergencyDialerIntent(
                        null /* number */);
                intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
                        | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
                        | Intent.FLAG_ACTIVITY_CLEAR_TOP);
                intent.putExtra(EmergencyDialerConstants.EXTRA_ENTRY_TYPE,
                        EmergencyDialerConstants.ENTRY_TYPE_POWER_MENU);
                mContext.startActivityAsUser(intent, UserHandle.CURRENT);
            }
        }
    }

    @VisibleForTesting
    EmergencyDialerAction makeEmergencyDialerActionForTesting() {
        return new EmergencyDialerAction();
    }

    @VisibleForTesting
    final class RestartAction extends SinglePressAction implements LongPressAction {
        private RestartAction() {
            super(R.drawable.ic_restart, R.string.global_action_restart);
        }

        @Override
        public boolean onLongPress() {
            Log.d(TAG,"RestartAction::onLongPress()");
            if (!mUserManager.hasUserRestriction(UserManager.DISALLOW_SAFE_BOOT)) {
                mWindowManagerFuncs.reboot(true);
                return true;
            }
            return false;
        }

        @Override
        public boolean showDuringKeyguard() {
            return true;
        }

        @Override
        public boolean showBeforeProvisioning() {
            return true;
        }

        @Override
        public void onPress() {
            Log.d(TAG,"RestartAction::onPress()");
            mWindowManagerFuncs.reboot(false);
        }
    }

    @VisibleForTesting
    class ScreenshotAction extends SinglePressAction implements LongPressAction {
        public ScreenshotAction() {
            super(R.drawable.ic_screenshot, R.string.global_action_screenshot);
        }

        @Override
        public void onPress() {
            // Add a little delay before executing, to give the
            // dialog a chance to go away before it takes a
            // screenshot.
            // TODO: instead, omit global action dialog layer
            mHandler.postDelayed(new Runnable() {
                @Override
                public void run() {
                    mScreenshotHelper.takeScreenshot(TAKE_SCREENSHOT_FULLSCREEN, true, true,
                            SCREENSHOT_GLOBAL_ACTIONS, mHandler, null);
                    mMetricsLogger.action(MetricsEvent.ACTION_SCREENSHOT_POWER_MENU);
                    mUiEventLogger.log(GlobalActionsEvent.GA_SCREENSHOT_PRESS);
                }
            }, mDialogPressDelay);
        }

        @Override
        public boolean showDuringKeyguard() {
            return true;
        }

        @Override
        public boolean showBeforeProvisioning() {
            return false;
        }

        @Override
        public boolean onLongPress() {
            if (FeatureFlagUtils.isEnabled(mContext, FeatureFlagUtils.SCREENRECORD_LONG_PRESS)) {
                mUiEventLogger.log(GlobalActionsEvent.GA_SCREENSHOT_LONG_PRESS);
                mScreenRecordHelper.launchRecordPrompt();
            } else {
                onPress();
            }
            return true;
        }
    }

    @VisibleForTesting
    ScreenshotAction makeScreenshotActionForTesting() {
        return new ScreenshotAction();
    }

    @VisibleForTesting
    class BugReportAction extends SinglePressAction implements LongPressAction {

        public BugReportAction() {
            super(R.drawable.ic_lock_bugreport, R.string.bugreport_title);
        }

        @Override
        public void onPress() {
            // don't actually trigger the bugreport if we are running stability
            // tests via monkey
            if (ActivityManager.isUserAMonkey()) {
                return;
            }
            // Add a little delay before executing, to give the
            // dialog a chance to go away before it takes a
            // screenshot.
            mHandler.postDelayed(new Runnable() {
                @Override
                public void run() {
                    try {
                        // Take an "interactive" bugreport.
                        mMetricsLogger.action(
                                MetricsEvent.ACTION_BUGREPORT_FROM_POWER_MENU_INTERACTIVE);
                        mUiEventLogger.log(GlobalActionsEvent.GA_BUGREPORT_PRESS);
                        if (!mIActivityManager.launchBugReportHandlerApp()) {
                            Log.w(TAG, "Bugreport handler could not be launched");
                            mIActivityManager.requestInteractiveBugReport();
                        }
                    } catch (RemoteException e) {
                    }
                }
            }, mDialogPressDelay);
        }

        @Override
        public boolean onLongPress() {
            // don't actually trigger the bugreport if we are running stability
            // tests via monkey
            if (ActivityManager.isUserAMonkey()) {
                return false;
            }
            try {
                // Take a "full" bugreport.
                mMetricsLogger.action(MetricsEvent.ACTION_BUGREPORT_FROM_POWER_MENU_FULL);
                mUiEventLogger.log(GlobalActionsEvent.GA_BUGREPORT_LONG_PRESS);
                mIActivityManager.requestFullBugReport();
            } catch (RemoteException e) {
            }
            return false;
        }

        public boolean showDuringKeyguard() {
            return true;
        }

        @Override
        public boolean showBeforeProvisioning() {
            return false;
        }
    }

    @VisibleForTesting
    BugReportAction makeBugReportActionForTesting() {
        return new BugReportAction();
    }

    private final class LogoutAction extends SinglePressAction {
        private LogoutAction() {
            super(R.drawable.ic_logout, R.string.global_action_logout);
        }

        @Override
        public boolean showDuringKeyguard() {
            return true;
        }

        @Override
        public boolean showBeforeProvisioning() {
            return false;
        }

        @Override
        public void onPress() {
            // Add a little delay before executing, to give the dialog a chance to go away before
            // switching user
            mHandler.postDelayed(() -> {
                try {
                    int currentUserId = getCurrentUser().id;
                    mIActivityManager.switchUser(UserHandle.USER_SYSTEM);
                    mIActivityManager.stopUser(currentUserId, true /*force*/, null);
                } catch (RemoteException re) {
                    Log.e(TAG, "Couldn't logout user " + re);
                }
            }, mDialogPressDelay);
        }
    }

    private Action getSettingsAction() {
        return new SinglePressAction(R.drawable.ic_settings,
                R.string.global_action_settings) {

            @Override
            public void onPress() {
                Intent intent = new Intent(Settings.ACTION_SETTINGS);
                intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
                mContext.startActivity(intent);
            }

            @Override
            public boolean showDuringKeyguard() {
                return true;
            }

            @Override
            public boolean showBeforeProvisioning() {
                return true;
            }
        };
    }

    private Action getAssistAction() {
        return new SinglePressAction(R.drawable.ic_action_assist_focused,
                R.string.global_action_assist) {
            @Override
            public void onPress() {
                Intent intent = new Intent(Intent.ACTION_ASSIST);
                intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
                mContext.startActivity(intent);
            }

            @Override
            public boolean showDuringKeyguard() {
                return true;
            }

            @Override
            public boolean showBeforeProvisioning() {
                return true;
            }
        };
    }

    private Action getVoiceAssistAction() {
        return new SinglePressAction(R.drawable.ic_voice_search,
                R.string.global_action_voice_assist) {
            @Override
            public void onPress() {
                Intent intent = new Intent(Intent.ACTION_VOICE_ASSIST);
                intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
                mContext.startActivity(intent);
            }

            @Override
            public boolean showDuringKeyguard() {
                return true;
            }

            @Override
            public boolean showBeforeProvisioning() {
                return true;
            }
        };
    }

    @VisibleForTesting
    class LockDownAction extends SinglePressAction {
        LockDownAction() {
            super(R.drawable.ic_lock_lockdown, R.string.global_action_lockdown);
        }

        @Override
        public void onPress() {
            mLockPatternUtils.requireStrongAuth(STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN,
                    UserHandle.USER_ALL);
            try {
                mIWindowManager.lockNow(null);
                // Lock profiles (if any) on the background thread.
                mBackgroundExecutor.execute(() -> lockProfiles());
            } catch (RemoteException e) {
                Log.e(TAG, "Error while trying to lock device.", e);
            }
        }

        @Override
        public boolean showDuringKeyguard() {
            return true;
        }

        @Override
        public boolean showBeforeProvisioning() {
            return false;
        }
    }

    private void lockProfiles() {
        final int currentUserId = getCurrentUser().id;
        final int[] profileIds = mUserManager.getEnabledProfileIds(currentUserId);
        for (final int id : profileIds) {
            if (id != currentUserId) {
                mTrustManager.setDeviceLockedForUser(id, true);
            }
        }
    }

    private UserInfo getCurrentUser() {
        try {
            return mIActivityManager.getCurrentUser();
        } catch (RemoteException re) {
            return null;
        }
    }

    /**
     * Non-thread-safe current user provider that caches the result - helpful when a method needs
     * to fetch it an indeterminate number of times.
     */
    private class CurrentUserProvider {
        private UserInfo mUserInfo = null;
        private boolean mFetched = false;

        @Nullable
        UserInfo get() {
            if (!mFetched) {
                mFetched = true;
                mUserInfo = getCurrentUser();
            }
            return mUserInfo;
        }
    }

    private void addUserActions(List<Action> actions, UserInfo currentUser) {
        if (mUserManager.isUserSwitcherEnabled()) {
            List<UserInfo> users = mUserManager.getUsers();
            for (final UserInfo user : users) {
                if (user.supportsSwitchToByUser()) {
                    boolean isCurrentUser = currentUser == null
                            ? user.id == 0 : (currentUser.id == user.id);
                    Drawable icon = user.iconPath != null ? Drawable.createFromPath(user.iconPath)
                            : null;
                    SinglePressAction switchToUser = new SinglePressAction(
                            R.drawable.ic_menu_cc, icon,
                            (user.name != null ? user.name : "Primary")
                                    + (isCurrentUser ? " \u2714" : "")) {
                        public void onPress() {
                            try {
                                mIActivityManager.switchUser(user.id);
                            } catch (RemoteException re) {
                                Log.e(TAG, "Couldn't switch user " + re);
                            }
                        }

                        public boolean showDuringKeyguard() {
                            return true;
                        }

                        public boolean showBeforeProvisioning() {
                            return false;
                        }
                    };
                    addIfShouldShowAction(actions, switchToUser);
                }
            }
        }
    }

    private void prepareDialog() {
        refreshSilentMode();
        mAirplaneModeOn.updateState(mAirplaneState);
        mAdapter.notifyDataSetChanged();
        mLifecycle.setCurrentState(Lifecycle.State.RESUMED);
    }

    private void refreshSilentMode() {
        if (!mHasVibrator) {
            Integer value = mRingerModeTracker.getRingerMode().getValue();
            final boolean silentModeOn = value != null && value != AudioManager.RINGER_MODE_NORMAL;
            ((ToggleAction) mSilentModeAction).updateState(
                    silentModeOn ? ToggleState.On : ToggleState.Off);
        }
    }

    /**
     * {@inheritDoc}
     */
    public void onDismiss(DialogInterface dialog) {
        if (mDialog == dialog) {
            mDialog = null;
        }
        mUiEventLogger.log(GlobalActionsEvent.GA_POWER_MENU_CLOSE);
        mWindowManagerFuncs.onGlobalActionsHidden();
        mLifecycle.setCurrentState(Lifecycle.State.CREATED);
    }

    /**
     * {@inheritDoc}
     */
    public void onShow(DialogInterface dialog) {
        mMetricsLogger.visible(MetricsEvent.POWER_MENU);
        mUiEventLogger.log(GlobalActionsEvent.GA_POWER_MENU_OPEN);
    }

    /**
     * The adapter used for power menu items shown in the global actions dialog.
     */
    public class MyAdapter extends MultiListAdapter {
        private int countItems(boolean separated) {
            int count = 0;
            for (int i = 0; i < mItems.size(); i++) {
                final Action action = mItems.get(i);

                if (action.shouldBeSeparated() == separated) {
                    count++;
                }
            }
            return count;
        }

        @Override
        public int countSeparatedItems() {
            return countItems(true);
        }

        @Override
        public int countListItems() {
            return countItems(false);
        }

        @Override
        public int getCount() {
            return countSeparatedItems() + countListItems();
        }

        @Override
        public boolean isEnabled(int position) {
            return getItem(position).isEnabled();
        }

        @Override
        public boolean areAllItemsEnabled() {
            return false;
        }

        @Override
        public Action getItem(int position) {
            int filteredPos = 0;
            for (int i = 0; i < mItems.size(); i++) {
                final Action action = mItems.get(i);
                if (!shouldShowAction(action)) {
                    continue;
                }
                if (filteredPos == position) {
                    return action;
                }
                filteredPos++;
            }

            throw new IllegalArgumentException("position " + position
                    + " out of range of showable actions"
                    + ", filtered count=" + getCount()
                    + ", keyguardshowing=" + mKeyguardShowing
                    + ", provisioned=" + mDeviceProvisioned);
        }


        public long getItemId(int position) {
            return position;
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            Action action = getItem(position);
            View view = action.create(mContext, convertView, parent, LayoutInflater.from(mContext));
            view.setOnClickListener(v -> onClickItem(position));
            if (action instanceof LongPressAction) {
                view.setOnLongClickListener(v -> onLongClickItem(position));
            }
            return view;
        }

        @Override
        public boolean onLongClickItem(int position) {
            final Action action = mAdapter.getItem(position);
            if (action instanceof LongPressAction) {
                if (mDialog != null) {
                    mDialog.dismiss();
                } else {
                    Log.w(TAG, "Action long-clicked while mDialog is null.");
                }
                return ((LongPressAction) action).onLongPress();
            }
            return false;
        }

        @Override
        public void onClickItem(int position) {
            Action item = mAdapter.getItem(position);
            if (!(item instanceof SilentModeTriStateAction)) {
                if (mDialog != null) {
                    // don't dismiss the dialog if we're opening the power options menu
                    if (!(item instanceof PowerOptionsAction)) {
                        mDialog.dismiss();
                    }
                } else {
                    Log.w(TAG, "Action clicked while mDialog is null.");
                }
                item.onPress();
            }
        }

        @Override
        public boolean shouldBeSeparated(int position) {
            return getItem(position).shouldBeSeparated();
        }
    }

    /**
     * The adapter used for items in the overflow menu.
     */
    public class MyPowerOptionsAdapter extends BaseAdapter {
        @Override
        public int getCount() {
            return mPowerItems.size();
        }

        @Override
        public Action getItem(int position) {
            return mPowerItems.get(position);
        }

        @Override
        public long getItemId(int position) {
            return position;
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            Action action = getItem(position);
            if (action == null) {
                Log.w(TAG, "No power options action found at position: " + position);
                return null;
            }
            int viewLayoutResource = com.android.systemui.R.layout.global_actions_power_item;
            View view = convertView != null ? convertView
                    : LayoutInflater.from(mContext).inflate(viewLayoutResource, parent, false);
            view.setOnClickListener(v -> onClickItem(position));
            if (action instanceof LongPressAction) {
                view.setOnLongClickListener(v -> onLongClickItem(position));
            }
            ImageView icon = view.findViewById(R.id.icon);
            TextView messageView = view.findViewById(R.id.message);
            messageView.setSelected(true); // necessary for marquee to work

            icon.setImageDrawable(action.getIcon(mContext));
            icon.setScaleType(ScaleType.CENTER_CROP);

            if (action.getMessage() != null) {
                messageView.setText(action.getMessage());
            } else {
                messageView.setText(action.getMessageResId());
            }
            return view;
        }

        private boolean onLongClickItem(int position) {
            final Action action = getItem(position);
            if (action instanceof LongPressAction) {
                if (mDialog != null) {
                    mDialog.dismiss();
                } else {
                    Log.w(TAG, "Action long-clicked while mDialog is null.");
                }
                return ((LongPressAction) action).onLongPress();
            }
            return false;
        }

        private void onClickItem(int position) {
            Action item = getItem(position);
            if (!(item instanceof SilentModeTriStateAction)) {
                if (mDialog != null) {
                    mDialog.dismiss();
                } else {
                    Log.w(TAG, "Action clicked while mDialog is null.");
                }
                item.onPress();
            }
        }
    }

    /**
     * The adapter used for items in the power options menu, triggered by the PowerOptionsAction.
     */
    public class MyOverflowAdapter extends BaseAdapter {
        @Override
        public int getCount() {
            return mOverflowItems.size();
        }

        @Override
        public Action getItem(int position) {
            return mOverflowItems.get(position);
        }

        @Override
        public long getItemId(int position) {
            return position;
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            Action action = getItem(position);
            if (action == null) {
                Log.w(TAG, "No overflow action found at position: " + position);
                return null;
            }
            int viewLayoutResource = com.android.systemui.R.layout.controls_more_item;
            View view = convertView != null ? convertView
                    : LayoutInflater.from(mContext).inflate(viewLayoutResource, parent, false);
            TextView textView = (TextView) view;
            if (action.getMessageResId() != 0) {
                textView.setText(action.getMessageResId());
            } else {
                textView.setText(action.getMessage());
            }
            return textView;
        }

        private boolean onLongClickItem(int position) {
            final Action action = getItem(position);
            if (action instanceof LongPressAction) {
                if (mDialog != null) {
                    mDialog.dismiss();
                } else {
                    Log.w(TAG, "Action long-clicked while mDialog is null.");
                }
                return ((LongPressAction) action).onLongPress();
            }
            return false;
        }

        private void onClickItem(int position) {
            Action item = getItem(position);
            if (!(item instanceof SilentModeTriStateAction)) {
                if (mDialog != null) {
                    mDialog.dismiss();
                } else {
                    Log.w(TAG, "Action clicked while mDialog is null.");
                }
                item.onPress();
            }
        }
    }

    // note: the scheme below made more sense when we were planning on having
    // 8 different things in the global actions dialog.  seems overkill with
    // only 3 items now, but may as well keep this flexible approach so it will
    // be easy should someone decide at the last minute to include something
    // else, such as 'enable wifi', or 'enable bluetooth'

    /**
     * What each item in the global actions dialog must be able to support.
     */
    public interface Action {
        /**
         * @return Text that will be announced when dialog is created.  null for none.
         */
        CharSequence getLabelForAccessibility(Context context);

        View create(Context context, View convertView, ViewGroup parent, LayoutInflater inflater);

        void onPress();

        /**
         * @return whether this action should appear in the dialog when the keygaurd is showing.
         */
        boolean showDuringKeyguard();

        /**
         * @return whether this action should appear in the dialog before the
         * device is provisioned.f
         */
        boolean showBeforeProvisioning();

        boolean isEnabled();

        default boolean shouldBeSeparated() {
            return false;
        }

        /**
         * Return the id of the message associated with this action, or 0 if it doesn't have one.
         * @return
         */
        int getMessageResId();

        /**
         * Return the icon drawable for this action.
         */
        Drawable getIcon(Context context);

        /**
         * Return the message associated with this action, or null if it doesn't have one.
         * @return
         */
        CharSequence getMessage();
    }

    /**
     * An action that also supports long press.
     */
    private interface LongPressAction extends Action {
        boolean onLongPress();
    }

    /**
     * A single press action maintains no state, just responds to a press and takes an action.
     */

    private abstract class SinglePressAction implements Action {
        private final int mIconResId;
        private final Drawable mIcon;
        private final int mMessageResId;
        private final CharSequence mMessage;

        protected SinglePressAction(int iconResId, int messageResId) {
            mIconResId = iconResId;
            mMessageResId = messageResId;
            mMessage = null;
            mIcon = null;
        }

        protected SinglePressAction(int iconResId, Drawable icon, CharSequence message) {
            mIconResId = iconResId;
            mMessageResId = 0;
            mMessage = message;
            mIcon = icon;
        }

        public boolean isEnabled() {
            return true;
        }

        public String getStatus() {
            return null;
        }

        abstract public void onPress();

        public CharSequence getLabelForAccessibility(Context context) {
            if (mMessage != null) {
                return mMessage;
            } else {
                return context.getString(mMessageResId);
            }
        }

        public int getMessageResId() {
            return mMessageResId;
        }

        public CharSequence getMessage() {
            return mMessage;
        }

        @Override
        public Drawable getIcon(Context context) {
            if (mIcon != null) {
                return mIcon;
            } else {
                return context.getDrawable(mIconResId);
            }
        }

        public View create(
                Context context, View convertView, ViewGroup parent, LayoutInflater inflater) {
            View v = inflater.inflate(com.android.systemui.R.layout.global_actions_grid_item_v2,
                    parent, false /* attach */);

            ImageView icon = v.findViewById(R.id.icon);
            TextView messageView = v.findViewById(R.id.message);
            messageView.setSelected(true); // necessary for marquee to work

            icon.setImageDrawable(getIcon(context));
            icon.setScaleType(ScaleType.CENTER_CROP);

            if (mMessage != null) {
                messageView.setText(mMessage);
            } else {
                messageView.setText(mMessageResId);
            }

            return v;
        }
    }

    private enum ToggleState {
        Off(false),
        TurningOn(true),
        TurningOff(true),
        On(false);

        private final boolean mInTransition;

        ToggleState(boolean intermediate) {
            mInTransition = intermediate;
        }

        public boolean inTransition() {
            return mInTransition;
        }
    }

    /**
     * A toggle action knows whether it is on or off, and displays an icon and status message
     * accordingly.
     */
    private abstract class ToggleAction implements Action {

        protected ToggleState mState = ToggleState.Off;

        // prefs
        protected int mEnabledIconResId;
        protected int mDisabledIconResid;
        protected int mMessageResId;
        protected int mEnabledStatusMessageResId;
        protected int mDisabledStatusMessageResId;

        /**
         * @param enabledIconResId           The icon for when this action is on.
         * @param disabledIconResid          The icon for when this action is off.
         * @param message                    The general information message, e.g 'Silent Mode'
         * @param enabledStatusMessageResId  The on status message, e.g 'sound disabled'
         * @param disabledStatusMessageResId The off status message, e.g. 'sound enabled'
         */
        public ToggleAction(int enabledIconResId,
                int disabledIconResid,
                int message,
                int enabledStatusMessageResId,
                int disabledStatusMessageResId) {
            mEnabledIconResId = enabledIconResId;
            mDisabledIconResid = disabledIconResid;
            mMessageResId = message;
            mEnabledStatusMessageResId = enabledStatusMessageResId;
            mDisabledStatusMessageResId = disabledStatusMessageResId;
        }

        /**
         * Override to make changes to resource IDs just before creating the View.
         */
        void willCreate() {

        }

        @Override
        public CharSequence getLabelForAccessibility(Context context) {
            return context.getString(mMessageResId);
        }

        private boolean isOn() {
            return mState == ToggleState.On || mState == ToggleState.TurningOn;
        }

        @Override
        public CharSequence getMessage() {
            return null;
        }
        @Override
        public int getMessageResId() {
            return isOn() ? mEnabledStatusMessageResId : mDisabledStatusMessageResId;
        }

        private int getIconResId() {
            return isOn() ? mEnabledIconResId : mDisabledIconResid;
        }

        @Override
        public Drawable getIcon(Context context) {
            return context.getDrawable(getIconResId());
        }

        public View create(Context context, View convertView, ViewGroup parent,
                LayoutInflater inflater) {
            willCreate();

            View v = inflater.inflate(com.android.systemui.R.layout.global_actions_grid_item_v2,
                    parent, false /* attach */);

            ImageView icon = (ImageView) v.findViewById(R.id.icon);
            TextView messageView = (TextView) v.findViewById(R.id.message);
            final boolean enabled = isEnabled();

            if (messageView != null) {
                messageView.setText(getMessageResId());
                messageView.setEnabled(enabled);
                messageView.setSelected(true); // necessary for marquee to work
            }

            if (icon != null) {
                icon.setImageDrawable(context.getDrawable(getIconResId()));
                icon.setEnabled(enabled);
            }

            v.setEnabled(enabled);

            return v;
        }

        public final void onPress() {
            if (mState.inTransition()) {
                Log.w(TAG, "shouldn't be able to toggle when in transition");
                return;
            }

            final boolean nowOn = !(mState == ToggleState.On);
            onToggle(nowOn);
            changeStateFromPress(nowOn);
        }

        public boolean isEnabled() {
            return !mState.inTransition();
        }

        /**
         * Implementations may override this if their state can be in on of the intermediate states
         * until some notification is received (e.g airplane mode is 'turning off' until we know the
         * wireless connections are back online
         *
         * @param buttonOn Whether the button was turned on or off
         */
        protected void changeStateFromPress(boolean buttonOn) {
            mState = buttonOn ? ToggleState.On : ToggleState.Off;
        }

        abstract void onToggle(boolean on);

        public void updateState(ToggleState state) {
            mState = state;
        }
    }

    private class AirplaneModeAction extends ToggleAction {
        AirplaneModeAction() {
            super(
                    R.drawable.ic_lock_airplane_mode,
                    R.drawable.ic_lock_airplane_mode_off,
                    R.string.global_actions_toggle_airplane_mode,
                    R.string.global_actions_airplane_mode_on_status,
                    R.string.global_actions_airplane_mode_off_status);
        }

        void onToggle(boolean on) {
            if (mHasTelephony && TelephonyProperties.in_ecm_mode().orElse(false)) {
                mIsWaitingForEcmExit = true;
                // Launch ECM exit dialog
                Intent ecmDialogIntent =
                        new Intent(TelephonyManager.ACTION_SHOW_NOTICE_ECM_BLOCK_OTHERS, null);
                ecmDialogIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                mContext.startActivity(ecmDialogIntent);
            } else {
                changeAirplaneModeSystemSetting(on);
            }
        }

        @Override
        protected void changeStateFromPress(boolean buttonOn) {
            if (!mHasTelephony) return;

            // In ECM mode airplane state cannot be changed
            if (!TelephonyProperties.in_ecm_mode().orElse(false)) {
                mState = buttonOn ? ToggleState.TurningOn : ToggleState.TurningOff;
                mAirplaneState = mState;
            }
        }

        public boolean showDuringKeyguard() {
            return true;
        }

        public boolean showBeforeProvisioning() {
            return false;
        }
    }

    private class SilentModeToggleAction extends ToggleAction {
        public SilentModeToggleAction() {
            super(R.drawable.ic_audio_vol_mute,
                    R.drawable.ic_audio_vol,
                    R.string.global_action_toggle_silent_mode,
                    R.string.global_action_silent_mode_on_status,
                    R.string.global_action_silent_mode_off_status);
        }

        void onToggle(boolean on) {
            if (on) {
                mAudioManager.setRingerMode(AudioManager.RINGER_MODE_SILENT);
            } else {
                mAudioManager.setRingerMode(AudioManager.RINGER_MODE_NORMAL);
            }
        }

        public boolean showDuringKeyguard() {
            return true;
        }

        public boolean showBeforeProvisioning() {
            return false;
        }
    }

    private static class SilentModeTriStateAction implements Action, View.OnClickListener {

        private final int[] ITEM_IDS = {R.id.option1, R.id.option2, R.id.option3};

        private final AudioManager mAudioManager;
        private final Handler mHandler;

        SilentModeTriStateAction(AudioManager audioManager, Handler handler) {
            mAudioManager = audioManager;
            mHandler = handler;
        }

        private int ringerModeToIndex(int ringerMode) {
            // They just happen to coincide
            return ringerMode;
        }

        private int indexToRingerMode(int index) {
            // They just happen to coincide
            return index;
        }

        @Override
        public CharSequence getLabelForAccessibility(Context context) {
            return null;
        }

        @Override
        public int getMessageResId() {
            return 0;
        }

        @Override
        public CharSequence getMessage() {
            return null;
        }

        @Override
        public Drawable getIcon(Context context) {
            return null;
        }


        public View create(Context context, View convertView, ViewGroup parent,
                LayoutInflater inflater) {
            View v = inflater.inflate(R.layout.global_actions_silent_mode, parent, false);

            int selectedIndex = ringerModeToIndex(mAudioManager.getRingerMode());
            for (int i = 0; i < 3; i++) {
                View itemView = v.findViewById(ITEM_IDS[i]);
                itemView.setSelected(selectedIndex == i);
                // Set up click handler
                itemView.setTag(i);
                itemView.setOnClickListener(this);
            }
            return v;
        }

        public void onPress() {
        }

        public boolean showDuringKeyguard() {
            return true;
        }

        public boolean showBeforeProvisioning() {
            return false;
        }

        public boolean isEnabled() {
            return true;
        }

        void willCreate() {
        }

        public void onClick(View v) {
            if (!(v.getTag() instanceof Integer)) return;

            int index = (Integer) v.getTag();
            mAudioManager.setRingerMode(indexToRingerMode(index));
            mHandler.sendEmptyMessageDelayed(MESSAGE_DISMISS, DIALOG_DISMISS_DELAY);
        }
    }

    private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();
            if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action)
                    || Intent.ACTION_SCREEN_OFF.equals(action)) {
                String reason = intent.getStringExtra(SYSTEM_DIALOG_REASON_KEY);
                if (!SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS.equals(reason)) {
                    mHandler.sendMessage(mHandler.obtainMessage(MESSAGE_DISMISS, reason));
                }
            } else if (TelephonyManager.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED.equals(action)) {
                // Airplane mode can be changed after ECM exits if airplane toggle button
                // is pressed during ECM mode
                if (!(intent.getBooleanExtra(TelephonyManager.EXTRA_PHONE_IN_ECM_STATE, false))
                        && mIsWaitingForEcmExit) {
                    mIsWaitingForEcmExit = false;
                    changeAirplaneModeSystemSetting(true);
                }
            }
        }
    };

    PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
        @Override
        public void onServiceStateChanged(ServiceState serviceState) {
            if (!mHasTelephony) return;
            final boolean inAirplaneMode = serviceState.getState() == ServiceState.STATE_POWER_OFF;
            mAirplaneState = inAirplaneMode ? ToggleState.On : ToggleState.Off;
            mAirplaneModeOn.updateState(mAirplaneState);
            mAdapter.notifyDataSetChanged();
            mOverflowAdapter.notifyDataSetChanged();
            mPowerAdapter.notifyDataSetChanged();
        }
    };

    private ContentObserver mAirplaneModeObserver = new ContentObserver(mMainHandler) {
        @Override
        public void onChange(boolean selfChange) {
            onAirplaneModeChanged();
        }
    };

    private static final int MESSAGE_DISMISS = 0;
    private static final int MESSAGE_REFRESH = 1;
    private static final int DIALOG_DISMISS_DELAY = 300; // ms
    private static final int DIALOG_PRESS_DELAY = 850; // ms

    @VisibleForTesting void setZeroDialogPressDelayForTesting() {
        mDialogPressDelay = 0; // ms
    }

    private Handler mHandler = new Handler() {
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case MESSAGE_DISMISS:
                    if (mDialog != null) {
                        if (SYSTEM_DIALOG_REASON_DREAM.equals(msg.obj)) {
                            mDialog.completeDismiss();
                        } else {
                            mDialog.dismiss();
                        }
                        mDialog = null;
                    }
                    break;
                case MESSAGE_REFRESH:
                    refreshSilentMode();
                    mAdapter.notifyDataSetChanged();
                    break;
            }
        }
    };

    private void onAirplaneModeChanged() {
        // Let the service state callbacks handle the state.
        if (mHasTelephony) return;

        boolean airplaneModeOn = Settings.Global.getInt(
                mContentResolver,
                Settings.Global.AIRPLANE_MODE_ON,
                0) == 1;
        mAirplaneState = airplaneModeOn ? ToggleState.On : ToggleState.Off;
        mAirplaneModeOn.updateState(mAirplaneState);
    }

    /**
     * Change the airplane mode system setting
     */
    private void changeAirplaneModeSystemSetting(boolean on) {
        Settings.Global.putInt(
                mContentResolver,
                Settings.Global.AIRPLANE_MODE_ON,
                on ? 1 : 0);
        Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
        intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
        intent.putExtra("state", on);
        mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
        if (!mHasTelephony) {
            mAirplaneState = on ? ToggleState.On : ToggleState.Off;
        }
    }

    @NonNull
    @Override
    public Lifecycle getLifecycle() {
        return mLifecycle;
    }

    @VisibleForTesting
    static final class ActionsDialog extends Dialog implements DialogInterface,
            ColorExtractor.OnColorsChangedListener {

        private final Context mContext;
        private final MyAdapter mAdapter;
        private final MyOverflowAdapter mOverflowAdapter;
        private final MyPowerOptionsAdapter mPowerOptionsAdapter;
        private final IStatusBarService mStatusBarService;
        private final IBinder mToken = new Binder();
        private MultiListLayout mGlobalActionsLayout;
        private Drawable mBackgroundDrawable;
        private final SysuiColorExtractor mColorExtractor;
        private final GlobalActionsPanelPlugin.PanelViewController mWalletViewController;
        private boolean mKeyguardShowing;
        private boolean mShowing;
        private float mScrimAlpha;
        private ResetOrientationData mResetOrientationData;
        private boolean mHadTopUi;
        private final NotificationShadeWindowController mNotificationShadeWindowController;
        private final NotificationShadeDepthController mDepthController;
        private final SysUiState mSysUiState;
        private ListPopupWindow mOverflowPopup;
        private Dialog mPowerOptionsDialog;
        private final Runnable mOnRotateCallback;
        private final boolean mControlsAvailable;

        private ControlsUiController mControlsUiController;
        private ViewGroup mControlsView;
        private ViewGroup mContainer;
        @VisibleForTesting ViewGroup mLockMessageContainer;
        private TextView mLockMessage;

        ActionsDialog(Context context, MyAdapter adapter, MyOverflowAdapter overflowAdapter,
                GlobalActionsPanelPlugin.PanelViewController walletViewController,
                NotificationShadeDepthController depthController,
                SysuiColorExtractor sysuiColorExtractor, IStatusBarService statusBarService,
                NotificationShadeWindowController notificationShadeWindowController,
                boolean controlsAvailable, @Nullable ControlsUiController controlsUiController,
                SysUiState sysuiState, Runnable onRotateCallback, boolean keyguardShowing,
                MyPowerOptionsAdapter powerAdapter) {
            super(context, com.android.systemui.R.style.Theme_SystemUI_Dialog_GlobalActions);
            mContext = context;
            mAdapter = adapter;
            mOverflowAdapter = overflowAdapter;
            mPowerOptionsAdapter = powerAdapter;
            mDepthController = depthController;
            mColorExtractor = sysuiColorExtractor;
            mStatusBarService = statusBarService;
            mNotificationShadeWindowController = notificationShadeWindowController;
            mControlsAvailable = controlsAvailable;
            mControlsUiController = controlsUiController;
            mSysUiState = sysuiState;
            mOnRotateCallback = onRotateCallback;
            mKeyguardShowing = keyguardShowing;

            // Window initialization
            Window window = getWindow();
            window.requestFeature(Window.FEATURE_NO_TITLE);
            // Inflate the decor view, so the attributes below are not overwritten by the theme.
            window.getDecorView();
            window.getAttributes().systemUiVisibility |= View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
                    | View.SYSTEM_UI_FLAG_LAYOUT_STABLE
                    | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION;
            window.setLayout(MATCH_PARENT, MATCH_PARENT);
            window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
            window.addFlags(
                    WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
                            | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
                            | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR
                            | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
                            | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
                            | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
            window.setType(WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY);
            window.getAttributes().setFitInsetsTypes(0 /* types */);
            setTitle(R.string.global_actions);

            mWalletViewController = walletViewController;
            initializeLayout();
        }

        private boolean isShowingControls() {
            return mControlsUiController != null;
        }

        private void showControls(ControlsUiController controller) {
            mControlsUiController = controller;
            mControlsUiController.show(mControlsView, this::dismissForControlsActivity);
        }

        private void initializeWalletView() {
            if (mWalletViewController == null || mWalletViewController.getPanelContent() == null) {
                return;
            }

            int rotation = RotationUtils.getRotation(mContext);
            boolean rotationLocked = RotationPolicy.isRotationLocked(mContext);
            if (rotation != RotationUtils.ROTATION_NONE) {
                if (rotationLocked) {
                    if (mResetOrientationData == null) {
                        mResetOrientationData = new ResetOrientationData();
                        mResetOrientationData.locked = true;
                        mResetOrientationData.rotation = rotation;
                    }

                    // Unlock rotation, so user can choose to rotate to portrait to see the panel.
                    // This call is posted so that the rotation does not change until post-layout,
                    // otherwise onConfigurationChanged() may not get invoked.
                    mGlobalActionsLayout.post(() ->
                            RotationPolicy.setRotationLockAtAngle(
                                    mContext, false, RotationUtils.ROTATION_NONE));
                }
            } else {
                if (!rotationLocked) {
                    if (mResetOrientationData == null) {
                        mResetOrientationData = new ResetOrientationData();
                        mResetOrientationData.locked = false;
                    }

                    // Lock to portrait, so the user doesn't accidentally hide the panel.
                    // This call is posted so that the rotation does not change until post-layout,
                    // otherwise onConfigurationChanged() may not get invoked.
                    mGlobalActionsLayout.post(() ->
                            RotationPolicy.setRotationLockAtAngle(
                                    mContext, true, RotationUtils.ROTATION_NONE));
                }

                // Disable rotation suggestions, if enabled
                setRotationSuggestionsEnabled(false);

                FrameLayout panelContainer =
                        findViewById(com.android.systemui.R.id.global_actions_wallet);
                FrameLayout.LayoutParams panelParams =
                        new FrameLayout.LayoutParams(
                                FrameLayout.LayoutParams.MATCH_PARENT,
                                FrameLayout.LayoutParams.MATCH_PARENT);
                if (!mControlsAvailable) {
                    panelParams.topMargin = mContext.getResources().getDimensionPixelSize(
                            com.android.systemui.R.dimen.global_actions_wallet_top_margin);
                }
                View walletView = mWalletViewController.getPanelContent();
                panelContainer.addView(walletView, panelParams);
                // Smooth transitions when wallet is resized, which can happen when a card is added
                ViewGroup root = findViewById(com.android.systemui.R.id.global_actions_grid_root);
                if (root != null) {
                    walletView.addOnLayoutChangeListener((v, l, t, r, b, ol, ot, or, ob) -> {
                        int oldHeight = ob - ot;
                        int newHeight = b - t;
                        if (oldHeight > 0 && oldHeight != newHeight) {
                            TransitionSet transition = new AutoTransition()
                                    .setDuration(250)
                                    .setOrdering(TransitionSet.ORDERING_TOGETHER);
                            TransitionManager.beginDelayedTransition(root, transition);
                        }
                    });
                }
            }
        }

        private ListPopupWindow createPowerOverflowPopup() {
            GlobalActionsPopupMenu popup = new GlobalActionsPopupMenu(
                    new ContextThemeWrapper(
                            mContext,
                            com.android.systemui.R.style.Control_ListPopupWindow
                    ), false /* isDropDownMode */);
            popup.setOnItemClickListener(
                    (parent, view, position, id) -> mOverflowAdapter.onClickItem(position));
            popup.setOnItemLongClickListener(
                    (parent, view, position, id) -> mOverflowAdapter.onLongClickItem(position));
            View overflowButton =
                    findViewById(com.android.systemui.R.id.global_actions_overflow_button);
            popup.setAnchorView(overflowButton);
            popup.setAdapter(mOverflowAdapter);
            return popup;
        }

        public void showPowerOptionsMenu() {
            mPowerOptionsDialog = GlobalActionsPowerDialog.create(mContext, mPowerOptionsAdapter);
            mPowerOptionsDialog.show();
        }

        private void showPowerOverflowMenu() {
            mOverflowPopup = createPowerOverflowPopup();
            mOverflowPopup.show();
        }

        private void initializeLayout() {
            setContentView(com.android.systemui.R.layout.global_actions_grid_v2);
            fixNavBarClipping();
            mControlsView = findViewById(com.android.systemui.R.id.global_actions_controls);
            mGlobalActionsLayout = findViewById(com.android.systemui.R.id.global_actions_view);
            mGlobalActionsLayout.setListViewAccessibilityDelegate(new View.AccessibilityDelegate() {
                @Override
                public boolean dispatchPopulateAccessibilityEvent(
                        View host, AccessibilityEvent event) {
                    // Populate the title here, just as Activity does
                    event.getText().add(mContext.getString(R.string.global_actions));
                    return true;
                }
            });
            mGlobalActionsLayout.setRotationListener(this::onRotate);
            mGlobalActionsLayout.setAdapter(mAdapter);
            mContainer = findViewById(com.android.systemui.R.id.global_actions_container);
            mLockMessageContainer = requireViewById(
                    com.android.systemui.R.id.global_actions_lock_message_container);
            mLockMessage = requireViewById(com.android.systemui.R.id.global_actions_lock_message);

            View overflowButton = findViewById(
                    com.android.systemui.R.id.global_actions_overflow_button);
            if (overflowButton != null) {
                if (mOverflowAdapter.getCount() > 0) {
                    overflowButton.setOnClickListener((view) -> showPowerOverflowMenu());
                    LinearLayout.LayoutParams params =
                            (LinearLayout.LayoutParams) mGlobalActionsLayout.getLayoutParams();
                    params.setMarginEnd(0);
                    mGlobalActionsLayout.setLayoutParams(params);
                } else {
                    overflowButton.setVisibility(View.GONE);
                    LinearLayout.LayoutParams params =
                            (LinearLayout.LayoutParams) mGlobalActionsLayout.getLayoutParams();
                    params.setMarginEnd(mContext.getResources().getDimensionPixelSize(
                            com.android.systemui.R.dimen.global_actions_side_margin));
                    mGlobalActionsLayout.setLayoutParams(params);
                }
            }

            initializeWalletView();
            if (mBackgroundDrawable == null) {
                mBackgroundDrawable = new ScrimDrawable();
                mScrimAlpha = 1.0f;
            }
            getWindow().setBackgroundDrawable(mBackgroundDrawable);
        }

        private void fixNavBarClipping() {
            ViewGroup content = findViewById(android.R.id.content);
            content.setClipChildren(false);
            content.setClipToPadding(false);
            ViewGroup contentParent = (ViewGroup) content.getParent();
            contentParent.setClipChildren(false);
            contentParent.setClipToPadding(false);
        }

        @Override
        protected void onStart() {
            super.setCanceledOnTouchOutside(true);
            super.onStart();
            mGlobalActionsLayout.updateList();

            if (mBackgroundDrawable instanceof ScrimDrawable) {
                mColorExtractor.addOnColorsChangedListener(this);
                GradientColors colors = mColorExtractor.getNeutralColors();
                updateColors(colors, false /* animate */);
            }
        }

        /**
         * Updates background and system bars according to current GradientColors.
         *
         * @param colors  Colors and hints to use.
         * @param animate Interpolates gradient if true, just sets otherwise.
         */
        private void updateColors(GradientColors colors, boolean animate) {
            if (!(mBackgroundDrawable instanceof ScrimDrawable)) {
                return;
            }
            ((ScrimDrawable) mBackgroundDrawable).setColor(Color.BLACK, animate);
            View decorView = getWindow().getDecorView();
            if (colors.supportsDarkText()) {
                decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR |
                        View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
            } else {
                decorView.setSystemUiVisibility(0);
            }
        }

        @Override
        protected void onStop() {
            super.onStop();
            mColorExtractor.removeOnColorsChangedListener(this);
        }

        @Override
        public void show() {
            super.show();
            mShowing = true;
            mHadTopUi = mNotificationShadeWindowController.getForceHasTopUi();
            mNotificationShadeWindowController.setForceHasTopUi(true);
            mSysUiState.setFlag(SYSUI_STATE_GLOBAL_ACTIONS_SHOWING, true)
                    .commitUpdate(mContext.getDisplayId());

            ViewGroup root = (ViewGroup) mGlobalActionsLayout.getRootView();
            root.setOnApplyWindowInsetsListener((v, windowInsets) -> {
                root.setPadding(windowInsets.getStableInsetLeft(),
                        windowInsets.getStableInsetTop(),
                        windowInsets.getStableInsetRight(),
                        windowInsets.getStableInsetBottom());
                return WindowInsets.CONSUMED;
            });
            if (mControlsUiController != null) {
                mControlsUiController.show(mControlsView, this::dismissForControlsActivity);
            }

            mBackgroundDrawable.setAlpha(0);
            float xOffset = mGlobalActionsLayout.getAnimationOffsetX();
            ObjectAnimator alphaAnimator =
                    ObjectAnimator.ofFloat(mContainer, "alpha", 0f, 1f);
            alphaAnimator.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
            alphaAnimator.setDuration(183);
            alphaAnimator.addUpdateListener((animation) -> {
                float animatedValue = animation.getAnimatedFraction();
                int alpha = (int) (animatedValue * mScrimAlpha * 255);
                mBackgroundDrawable.setAlpha(alpha);
                mDepthController.updateGlobalDialogVisibility(animatedValue,
                        mGlobalActionsLayout);
            });

            ObjectAnimator xAnimator =
                    ObjectAnimator.ofFloat(mContainer, "translationX", xOffset, 0f);
            xAnimator.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
            xAnimator.setDuration(350);

            AnimatorSet animatorSet = new AnimatorSet();
            animatorSet.playTogether(alphaAnimator, xAnimator);
            animatorSet.start();
        }

        @Override
        public void dismiss() {
            dismissWithAnimation(() -> {
                mContainer.setTranslationX(0);
                ObjectAnimator alphaAnimator =
                        ObjectAnimator.ofFloat(mContainer, "alpha", 1f, 0f);
                alphaAnimator.setInterpolator(Interpolators.FAST_OUT_LINEAR_IN);
                alphaAnimator.setDuration(233);
                alphaAnimator.addUpdateListener((animation) -> {
                    float animatedValue = 1f - animation.getAnimatedFraction();
                    int alpha = (int) (animatedValue * mScrimAlpha * 255);
                    mBackgroundDrawable.setAlpha(alpha);
                    mDepthController.updateGlobalDialogVisibility(animatedValue,
                            mGlobalActionsLayout);
                });

                float xOffset = mGlobalActionsLayout.getAnimationOffsetX();
                ObjectAnimator xAnimator =
                        ObjectAnimator.ofFloat(mContainer, "translationX", 0f, xOffset);
                xAnimator.setInterpolator(Interpolators.FAST_OUT_LINEAR_IN);
                xAnimator.setDuration(350);

                AnimatorSet animatorSet = new AnimatorSet();
                animatorSet.playTogether(alphaAnimator, xAnimator);
                animatorSet.addListener(new AnimatorListenerAdapter() {
                    public void onAnimationEnd(Animator animation) {
                        completeDismiss();
                    }
                });

                animatorSet.start();

                // close first, as popup windows will not fade during the animation
                dismissOverflow(false);
                dismissPowerOptions(false);
                if (mControlsUiController != null) mControlsUiController.closeDialogs(false);
            });
        }

        private void dismissForControlsActivity() {
            dismissWithAnimation(() -> {
                ViewGroup root = (ViewGroup) mGlobalActionsLayout.getParent();
                ControlsAnimations.exitAnimation(root, this::completeDismiss).start();
            });
        }

        void dismissWithAnimation(Runnable animation) {
            if (!mShowing) {
                return;
            }
            mShowing = false;
            animation.run();
        }

        private void completeDismiss() {
            mShowing = false;
            resetOrientation();
            dismissWallet();
            dismissOverflow(true);
            dismissPowerOptions(true);
            if (mControlsUiController != null) mControlsUiController.hide();
            mNotificationShadeWindowController.setForceHasTopUi(mHadTopUi);
            mDepthController.updateGlobalDialogVisibility(0, null /* view */);
            mSysUiState.setFlag(SYSUI_STATE_GLOBAL_ACTIONS_SHOWING, false)
                    .commitUpdate(mContext.getDisplayId());
            super.dismiss();
        }

        private void dismissWallet() {
            if (mWalletViewController != null) {
                mWalletViewController.onDismissed();
            }
        }

        private void dismissOverflow(boolean immediate) {
            if (mOverflowPopup != null) {
                if (immediate) {
                    mOverflowPopup.dismissImmediate();
                } else {
                    mOverflowPopup.dismiss();
                }
            }
        }

        private void dismissPowerOptions(boolean immediate) {
            if (mPowerOptionsDialog != null) {
                if (immediate) {
                    mPowerOptionsDialog.dismiss();
                } else {
                    mPowerOptionsDialog.dismiss();
                }
            }
        }

        private void setRotationSuggestionsEnabled(boolean enabled) {
            try {
                final int userId = Binder.getCallingUserHandle().getIdentifier();
                final int what = enabled
                        ? StatusBarManager.DISABLE2_NONE
                        : StatusBarManager.DISABLE2_ROTATE_SUGGESTIONS;
                mStatusBarService.disable2ForUser(what, mToken, mContext.getPackageName(), userId);
            } catch (RemoteException ex) {
                throw ex.rethrowFromSystemServer();
            }
        }

        private void resetOrientation() {
            if (mResetOrientationData != null) {
                RotationPolicy.setRotationLockAtAngle(mContext, mResetOrientationData.locked,
                        mResetOrientationData.rotation);
            }
            setRotationSuggestionsEnabled(true);
        }

        @Override
        public void onColorsChanged(ColorExtractor extractor, int which) {
            if (mKeyguardShowing) {
                if ((WallpaperManager.FLAG_LOCK & which) != 0) {
                    updateColors(extractor.getColors(WallpaperManager.FLAG_LOCK),
                            true /* animate */);
                }
            } else {
                if ((WallpaperManager.FLAG_SYSTEM & which) != 0) {
                    updateColors(extractor.getColors(WallpaperManager.FLAG_SYSTEM),
                            true /* animate */);
                }
            }
        }

        public void setKeyguardShowing(boolean keyguardShowing) {
            mKeyguardShowing = keyguardShowing;
        }

        public void refreshDialog() {
            // ensure dropdown menus are dismissed before re-initializing the dialog
            dismissWallet();
            dismissOverflow(true);
            dismissPowerOptions(true);
            if (mControlsUiController != null) {
                mControlsUiController.hide();
            }

            // re-create dialog
            initializeLayout();
            mGlobalActionsLayout.updateList();
            if (mControlsUiController != null) {
                mControlsUiController.show(mControlsView, this::dismissForControlsActivity);
            }
        }

        public void onRotate(int from, int to) {
            if (mShowing) {
                mOnRotateCallback.run();
                refreshDialog();
            }
        }

        void hideLockMessage() {
            if (mLockMessageContainer.getVisibility() == View.VISIBLE) {
                mLockMessageContainer.animate().alpha(0).setDuration(150).setListener(
                        new AnimatorListenerAdapter() {
                            @Override
                            public void onAnimationEnd(Animator animation) {
                                mLockMessageContainer.setVisibility(View.GONE);
                            }
                        }).start();
            }
        }

        void showLockMessage() {
            Drawable lockIcon = mContext.getDrawable(com.android.internal.R.drawable.ic_lock);
            lockIcon.setTint(mContext.getColor(com.android.systemui.R.color.control_primary_text));
            mLockMessage.setCompoundDrawablesWithIntrinsicBounds(null, lockIcon, null, null);
            mLockMessageContainer.setVisibility(View.VISIBLE);
        }

        private static class ResetOrientationData {
            public boolean locked;
            public int rotation;
        }
    }

    /**
     * Determines whether or not debug mode has been activated for the Global Actions Panel.
     */
    private static boolean isPanelDebugModeEnabled(Context context) {
        return Settings.Secure.getInt(context.getContentResolver(),
                Settings.Secure.GLOBAL_ACTIONS_PANEL_DEBUG_ENABLED, 0) == 1;
    }

    /**
     * Determines whether or not the Global Actions menu should be forced to use the newer
     * grid-style layout.
     */
    private static boolean isForceGridEnabled(Context context) {
        return isPanelDebugModeEnabled(context);
    }

    private boolean shouldShowControls() {
        boolean showOnLockScreen = mShowLockScreenCardsAndControls && mLockPatternUtils
                .getStrongAuthForUser(getCurrentUser().id) != STRONG_AUTH_REQUIRED_AFTER_BOOT;
        return controlsAvailable()
                && (mKeyguardStateController.isUnlocked() || showOnLockScreen);
    }

    private boolean controlsAvailable() {
        return mDeviceProvisioned
                && mControlsUiControllerOptional.isPresent()
                && mControlsUiControllerOptional.get().getAvailable()
                && !mControlsServiceInfos.isEmpty();
    }

    private boolean walletViewAvailable() {
        GlobalActionsPanelPlugin.PanelViewController walletViewController =
                getWalletViewController();
        return walletViewController != null && walletViewController.getPanelContent() != null;
    }

    private boolean shouldShowLockMessage() {
        boolean isLockedAfterBoot = mLockPatternUtils.getStrongAuthForUser(getCurrentUser().id)
                == STRONG_AUTH_REQUIRED_AFTER_BOOT;
        return !mKeyguardStateController.isUnlocked()
                && (!mShowLockScreenCardsAndControls || isLockedAfterBoot)
                && (controlsAvailable() || walletViewAvailable());
    }

    private void onPowerMenuLockScreenSettingsChanged() {
        mShowLockScreenCardsAndControls = Settings.Secure.getInt(mContentResolver,
                Settings.Secure.POWER_MENU_LOCKED_SHOW_CONTENT, 0) != 0;
    }
}

五、结语


总得来说Android 11.0最终调用SystemUI中的关机界面GlobalActionsDialog 这个过程非常的繁琐,可能是Android 11.0的关机界面拓展了很多功能,所以放到SystemUI中去实现。本文主要还是记录一个跟代码的过程。

  • 8
    点赞
  • 41
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论
### 回答1: Android 11.0最新的framework解析涵盖了多个方面的更新和改进。其中一些最重要的变化包括: 1.隐私和安全方面 隐私和安全一直是Android系统的重要关注点,Android 11.0有了更多的隐私和安全改进。它包括了一些额外的控制和新的API,以更好地保护用户的隐私和个人数据。例如,用户现在可以授予应用访问粘贴板的临时权限,应用需要经过授权才能够读取或写入到粘贴板中。同时,Android 11.0还增加了更多的双向加密通信方式,使得用户与设备之间的数据传输更为安全。 2.无线连接和协同功能方面 Android 11.0对于无线连接和协同功能的支持也得到了加强。比如,更好的5G支持使得设备更容易获得更高的网络速度和更好的连接质量。同时,Android 11.0引入了一些新的API,以更好地支持设备之间协同和合作工作。例如设备控制和文件共享等功能。 3.通知和消息方面 通知和消息管理也是Android 11.0框架中的一个重要方面。系统现在支持更多的通知类型,并增强了通知之间的分类和管理方式。这使得用户可以更快地找到最重要的通知,而不会被其它无用的通知干扰。此外,Android 11.0还增加了新的消息API,并简化了消息管理的过程,以更好地提供实时协作和交流功能。 总体而言,Android 11.0框架的更新和改进是有助于提高设备性能和用户体验的。虽然这只是一些框架更新的简要概述,但它们对于开发人员和最终用户都有着重要的影响,并为他们提供了更好的使用体验和功能。 ### 回答2: Android 11.0是谷歌公司推出的最新版本操作系统。这个版本的更新主要在软件的框架上。最新的Android 11.0针对开发者进行了一些重要的改进。该版本的框架包含几项新特性: 1. 安全性质 Android 11.0修改了文件管理器和匿名设备API等部分,增强了安全性能。使用者在安全性上更可靠。 2. 人工智能应用 谷歌强化了人工智能方面的功能,增加了人工智能应用框架。这使得开发者可以通过编写程序来创建复杂的人工智能应用。 3. 通信场景优化 将来多数的通信 scenario 进行了优化、增强。Android 11.0 为用户和开发者提供更丰富、更智能的通信场景。 4. 自动焦点 Android 11.0 增加了自动焦点功能。这个特性会根据用户使用时间、使用场景自动选择程序。这使得用户可以更方便地使用应用程序。 总体来说,Android 11.0的新框架解析增加了安全性、智能应用和通信场景优化。在这些方面,该版本的框架更加高效、灵活,更加适合开发者。这对于未来的移动发展趋势非常有意义。
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Mrsongs的心情杂货铺

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值