概述
GlobalActionsComponent用来显示长按电源键后出现的菜单界面和关机界面,并将相关事件发送给StatusBarManagerService去处理。
GlobalActionsComponent
GlobalActionsComponent继承SystemUI,实现了Callbacks, GlobalActionsManager接口
- SystemUI 的实现
//GlobalActionsComponent extends SystemUI implements Callbacks, GlobalActionsManager
private GlobalActions mPlugin;//GlobalActionsImpl对象
private Extension<GlobalActions> mExtension;//扩展
private IStatusBarService mBarService;//IStatusBarService 的代理对象
@Override
public void start() {
mBarService = IStatusBarService.Stub.asInterface(
ServiceManager.getService(Context.STATUS_BAR_SERVICE));//获取IStatusBarService 的代理对象 用于通知状态的变化
mExtension = Dependency.get(ExtensionController.class).newExtension(GlobalActions.class)
.withPlugin(GlobalActions.class)
.withDefault(() -> new GlobalActionsImpl(mContext))//指明是GlobalActionsImpl
.withCallback(this::onExtensionCallback)//回调
.build();
mPlugin = mExtension.get();
SysUiServiceProvider.getComponent(mContext, CommandQueue.class).addCallback(this);//将Callbacks添加到CommandQueue
}
private void onExtensionCallback(GlobalActions newPlugin) {//
if (mPlugin != null) {
mPlugin.destroy();//销毁旧的GlobalActionsImpl对象
}
mPlugin = newPlugin;
}
- GlobalActionsManager的实现
@Override
public void onGlobalActionsShown() {
try {
mBarService.onGlobalActionsShown();
} catch (RemoteException e) {
}
}
@Override
public void onGlobalActionsHidden() {
try {
mBarService.onGlobalActionsHidden();
} catch (RemoteException e) {
}
}
@Override
public void shutdown() {
try {
mBarService.shutdown();
} catch (RemoteException e) {
}
}
@Override
public void reboot(boolean safeMode) {
try {
mBarService.reboot(safeMode);
} catch (RemoteException e) {
}
}
GlobalActionsManager在GlobalActions显示和隐藏、关机、重启时通知IStatusBarService
- Callbacks的实现
@Override
public void handleShowShutdownUi(boolean isReboot, String reason) {
mExtension.get().showShutdownUi(isReboot, reason);//显示关机界面
}
@Override
public void handleShowGlobalActionsMenu() {
mExtension.get().showGlobalActions(this);//显示GlobalActions
}
Callbacks被外部调用,然后调用GlobalActionsImpl的相关方法显示GlobalActions和关机界面
GlobalActions的显示
Power事件的传递
GlobalActions的显示从长按Power按键开始,底层将事件上报处理。
//InputDispatcher.cpp
void InputDispatcher::notifyKey(const NotifyKeyArgs* args) {
//...
mPolicy->interceptKeyBeforeQueueing(&event, /*byref*/ policyFlags);//mPolicy是NativeInputManager对象
//...
}
调用了interceptKeyBeforeQueueing方法
//com_android_server_input_InputManagerService.cpp
void NativeInputManager::interceptKeyBeforeQueueing(const KeyEvent* keyEvent,
uint32_t& policyFlags) {
ATRACE_CALL();
// Policy:
// - Ignore untrusted events and pass them along.
// - Ask the window manager what to do with normal events and trusted injected events.
// - For normal events wake and brighten the screen if currently off or dim.
bool interactive = mInteractive.load();
if (interactive) {
policyFlags |= POLICY_FLAG_INTERACTIVE;
}
if ((policyFlags & POLICY_FLAG_TRUSTED)) {
nsecs_t when = keyEvent->getEventTime();
JNIEnv* env = jniEnv();
jobject keyEventObj = android_view_KeyEvent_fromNative(env, keyEvent);
jint wmActions;
if (keyEventObj) {
wmActions = env->CallIntMethod(mServiceObj,
gServiceClassInfo.interceptKeyBeforeQueueing,
keyEventObj, policyFlags);
if (checkAndClearExceptionFromCallback(env, "interceptKeyBeforeQueueing")) {
wmActions = 0;
}
android_view_KeyEvent_recycle(env, keyEventObj);
env->DeleteLocalRef(keyEventObj);
} else {
ALOGE("Failed to obtain key event object for interceptKeyBeforeQueueing.");
wmActions = 0;
}
handleInterceptActions(wmActions, when, /*byref*/ policyFlags);
} else {
if (interactive) {
policyFlags |= POLICY_FLAG_PASS_TO_USER;
}
}
}
调用了InputManagerService的interceptKeyBeforeQueueing方法
//InputManagerService.java
private int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags) {
return mWindowManagerCallbacks.interceptKeyBeforeQueueing(event, policyFlags);//mWindowManagerCallbacks是InputManagerCallback类的对象
}
调用了InputManagerCallback的interceptKeyBeforeQueueing方法
//InputManagerCallback.java
@Override
public int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags) {
return mService.mPolicy.interceptKeyBeforeQueueing(event, policyFlags);//mService是WindowManagerService,mPolicy是PhoneWindowManager
}
调用了PhoneWindowManager的interceptKeyBeforeQueueing方法
//PhoneWindowManager.java
@Override
public int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags) {
//...
// Handle special keys.
switch (keyCode) {
//...
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);//Down事件处理
} else {
interceptPowerKeyUp(event, interactive, canceled);
}
break;
}
//...
}
//...
return result;
}
private void interceptPowerKeyDown(KeyEvent event, boolean interactive) {
//...
if (!mPowerKeyHandled) {
if (interactive) {
// When interactive, we're already awake.
// Wait for a long press or for the button to be released to decide what to do.
if (hasLongPressOnPowerBehavior()) {
if ((event.getFlags() & KeyEvent.FLAG_LONG_PRESS) != 0) {
powerLongPress();
} else {
Message msg = mHandler.obtainMessage(MSG_POWER_LONG_PRESS);
msg.setAsynchronous(true);
mHandler.sendMessageDelayed(msg,
ViewConfiguration.get(mContext).getDeviceGlobalActionKeyTimeout());
if (hasVeryLongPressOnPowerBehavior()) {
Message longMsg = mHandler.obtainMessage(MSG_POWER_VERY_LONG_PRESS);
longMsg.setAsynchronous(true);
mHandler.sendMessageDelayed(longMsg, mVeryLongPressTimeout);
}
}
}
} else {
wakeUpFromPowerKey(event.getDownTime());
if (mSupportLongPressPowerWhenNonInteractive && hasLongPressOnPowerBehavior()) {
if ((event.getFlags() & KeyEvent.FLAG_LONG_PRESS) != 0) {
powerLongPress();
} else {
Message msg = mHandler.obtainMessage(MSG_POWER_LONG_PRESS);
msg.setAsynchronous(true);
mHandler.sendMessageDelayed(msg,
ViewConfiguration.get(mContext).getDeviceGlobalActionKeyTimeout());
if (hasVeryLongPressOnPowerBehavior()) {
Message longMsg = mHandler.obtainMessage(MSG_POWER_VERY_LONG_PRESS);
longMsg.setAsynchronous(true);
mHandler.sendMessageDelayed(longMsg, mVeryLongPressTimeout);
}
}
mBeganFromNonInteractive = true;
} else {
final int maxCount = getMaxMultiPressPowerCount();
if (maxCount <= 1) {
mPowerKeyHandled = true;
} else {
mBeganFromNonInteractive = true;
}
}
}
}
}
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;
//...
}
}
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);
}
经过多个方法的调用最后创建了一个GlobalActions对象,调用了它的showDialog方法
//GlobalActions.java
public GlobalActions(Context context, WindowManagerFuncs windowManagerFuncs) {
mContext = context;
mHandler = new Handler();
mWindowManagerFuncs = windowManagerFuncs;
mGlobalActionsProvider = LocalServices.getService(GlobalActionsProvider.class);//mGlobalActionsProvider在StatusBarManagerService中被创建
if (mGlobalActionsProvider != null) {
mGlobalActionsProvider.setGlobalActionsListener(this);//设置监听
} else {
Slog.i(TAG, "No GlobalActionsProvider found, defaulting to LegacyGlobalActions");
}
}
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) {//SystemUI的实现
mHandler.postDelayed(mShowTimeout, 5000);
mGlobalActionsProvider.showGlobalActions();
} else {//Framework的实现 与SystemUI类似
// SysUI isn't alive, show legacy menu.
ensureLegacyCreated();
mLegacyGlobalActions.showDialog(mKeyguardShowing, mDeviceProvisioned);
}
}
@Override
public void onGlobalActionsAvailableChanged(boolean available) {//监听回调
if (DEBUG) Slog.d(TAG, "onGlobalActionsAvailableChanged " + available);
mGlobalActionsAvailable = available;
if (mShowing && !mGlobalActionsAvailable) {
// Global actions provider died but we need to be showing global actions still, show the
// legacy global acrions provider.
ensureLegacyCreated();
mLegacyGlobalActions.showDialog(mKeyguardShowing, mDeviceProvisioned);
}
}
GlobalActions获取了GlobalActionsProvider,并向它注册了监听,监听中会确定mGlobalActionsAvailable的值,showDialog方法中有2个分支,Framework和SystemUI都有对GlobalActions的实现,mGlobalActionsAvailable决定执行哪个分支。
//StatusBarManagerService.java
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);//注册监听时马上回调,mBar为IStatusBar 也就是CommandQueue,回调确定mGlobalActionsAvailable的值
}
@Override
public void showGlobalActions() {
if (mBar != null) {
try {
mBar.showGlobalActionsMenu();
} catch (RemoteException ex) {}
}
}
};
调用了CommandQueue的showGlobalActionsMenu方法
//CommandQueue.java
@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();
}
break;
主线程处理消息时调用了Callbacks的handleShowGlobalActionsMenu方法。
GlobalActionsComponent实现了Callbacks接口的handleShowGlobalActionsMenu方法,并通过addCallback方法添加到了CommandQueue中,所以GlobalActionsComponent的handleShowGlobalActionsMenu方法会被调用,这样,GlobalActionsComponent就收到了外部请求显示GlobalActions。
GlobalActions的显示
从GlobalActionsComponent的Callbacks实现可以知道,这里调用了GlobalActionsImpl的showGlobalActions方法。
//GlobalActionsImpl.java
@Override
public void showGlobalActions(GlobalActionsManager manager) {
if (mDisabled) return;
if (mGlobalActions == null) {
mGlobalActions = new GlobalActionsDialog(mContext, manager);
}
mGlobalActions.showDialog(mKeyguardMonitor.isShowing(),
mDeviceProvisionedController.isDeviceProvisioned(),
mPanelExtension.get());
KeyguardUpdateMonitor.getInstance(mContext).requestFaceAuth();
}
创建了GlobalActionsDialog,并调用了它的showDialog方法
//GlobalActionsDialog.java
public void showDialog(boolean keyguardShowing, boolean isDeviceProvisioned,
GlobalActionsPanelPlugin panelPlugin) {
mKeyguardShowing = keyguardShowing;
mDeviceProvisioned = isDeviceProvisioned;
mPanelPlugin = panelPlugin;
if (mDialog != null) {
mDialog.dismiss();//销毁之前的mDialog
mDialog = null;
mHandler.sendEmptyMessage(MESSAGE_SHOW);//这里也是调用了handleShow
} else {
handleShow();
}
}
showDialog方法最终调用了handleShow方法
//GlobalActionsDialog.java
private void handleShow() {
awakenIfNecessary();
mDialog = createDialog();//创建ActionsDialog
prepareDialog();//更新了静音模式和飞行模式的状态
// If we only have 1 item and it's a simple press action, just do this action.
if (mAdapter.getCount() == 1
&& mAdapter.getItem(0) instanceof SinglePressAction
&& !(mAdapter.getItem(0) instanceof LongPressAction)) {
((SinglePressAction) mAdapter.getItem(0)).onPress();//如果只有一项,并且是单击项,直接触发它的onPress事件
} else {
WindowManager.LayoutParams attrs = mDialog.getWindow().getAttributes();
attrs.setTitle("ActionsDialog");
attrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
mDialog.getWindow().setAttributes(attrs);
mDialog.show();//显示对话框
mWindowManagerFuncs.onGlobalActionsShown();//mWindowManagerFuncs就是GlobalActionsManager,这里最终调用到了IStatusBarService 的方法
}
}
//GlobalActionsDialog.java
private ActionsDialog createDialog() {
// 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 ToggleAction(
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 && Boolean.parseBoolean(
SystemProperties.get(TelephonyProperties.PROPERTY_INECM_MODE))) {
mIsWaitingForEcmExit = true;
// Launch ECM exit dialog
Intent ecmDialogIntent =
new Intent(TelephonyIntents.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 (!(Boolean.parseBoolean(
SystemProperties.get(TelephonyProperties.PROPERTY_INECM_MODE)))) {
mState = buttonOn ? State.TurningOn : State.TurningOff;
mAirplaneState = mState;
}
}
public boolean showDuringKeyguard() {
return true;
}
public boolean showBeforeProvisioning() {
return false;
}
};
onAirplaneModeChanged();
mItems = new ArrayList<Action>();
String[] defaultActions = mContext.getResources().getStringArray(
R.array.config_globalActionsList);
ArraySet<String> addedKeys = new ArraySet<String>();
mHasLogoutButton = false;
mHasLockdownButton = false;
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)) {
mItems.add(new PowerAction());//关机
} else if (GLOBAL_ACTION_KEY_AIRPLANE.equals(actionKey)) {
mItems.add(mAirplaneModeOn);//飞行模式
} else if (GLOBAL_ACTION_KEY_BUGREPORT.equals(actionKey)) {
if (Settings.Global.getInt(mContext.getContentResolver(),
Settings.Global.BUGREPORT_IN_POWER_MENU, 0) != 0 && isCurrentUserOwner()) {
mItems.add(new BugReportAction());//Bug反馈
}
} else if (GLOBAL_ACTION_KEY_SILENT.equals(actionKey)) {
if (mShowSilentToggle) {
mItems.add(mSilentModeAction);//静音模式
}
} else if (GLOBAL_ACTION_KEY_USERS.equals(actionKey)) {
if (SystemProperties.getBoolean("fw.power_user_switcher", false)) {
addUsersToMenu(mItems);
}
} else if (GLOBAL_ACTION_KEY_SETTINGS.equals(actionKey)) {
mItems.add(getSettingsAction());//设置
} else if (GLOBAL_ACTION_KEY_LOCKDOWN.equals(actionKey)) {
if (Settings.Secure.getIntForUser(mContext.getContentResolver(),
Settings.Secure.LOCKDOWN_IN_POWER_MENU, 0, getCurrentUser().id) != 0
&& shouldDisplayLockdown()) {
mItems.add(getLockdownAction());
mHasLockdownButton = true;
}
} else if (GLOBAL_ACTION_KEY_VOICEASSIST.equals(actionKey)) {
mItems.add(getVoiceAssistAction());
} else if (GLOBAL_ACTION_KEY_ASSIST.equals(actionKey)) {
mItems.add(getAssistAction());
} else if (GLOBAL_ACTION_KEY_RESTART.equals(actionKey)) {
mItems.add(new RestartAction());//重启
} else if (GLOBAL_ACTION_KEY_SCREENSHOT.equals(actionKey)) {
mItems.add(new ScreenshotAction());//截屏
} else if (GLOBAL_ACTION_KEY_LOGOUT.equals(actionKey)) {
if (mDevicePolicyManager.isLogoutEnabled()
&& getCurrentUser().id != UserHandle.USER_SYSTEM) {
mItems.add(new LogoutAction());
mHasLogoutButton = true;
}
} else if (GLOBAL_ACTION_KEY_EMERGENCY.equals(actionKey)) {
if (!mEmergencyAffordanceManager.needsEmergencyAffordance()) {
mItems.add(new EmergencyDialerAction());
}
} else {
Log.e(TAG, "Invalid global action key " + actionKey);
}
// Add here so we don't add more than one.
addedKeys.add(actionKey);
}
if (mEmergencyAffordanceManager.needsEmergencyAffordance()) {
mItems.add(new EmergencyAffordanceAction());
}
mAdapter = new MyAdapter();//创建Adapter
GlobalActionsPanelPlugin.PanelViewController panelViewController =
mPanelPlugin != null
? mPanelPlugin.onPanelShown(
new GlobalActionsPanelPlugin.Callbacks() {
@Override
public void dismissGlobalActionsMenu() {
if (mDialog != null) {
mDialog.dismiss();
}
}
@Override
public void startPendingIntentDismissingKeyguard(
PendingIntent intent) {
mActivityStarter
.startPendingIntentDismissingKeyguard(intent);
}
},
mKeyguardManager.isDeviceLocked())
: null;
ActionsDialog dialog = new ActionsDialog(mContext, mAdapter, panelViewController);//根据Adapter创建ActionsDialog
dialog.setCanceledOnTouchOutside(false); // Handled by the custom class.
dialog.setKeyguardShowing(mKeyguardShowing);
dialog.setOnDismissListener(this);
dialog.setOnShowListener(this);
return dialog;
}
public void onDismiss(DialogInterface dialog) {
mWindowManagerFuncs.onGlobalActionsHidden();//调用GlobalActionsManager的方法,这里最终调用到了IStatusBarService 的方法
if (mShowSilentToggle) {
try {
mContext.unregisterReceiver(mRingerModeReceiver);
} catch (IllegalArgumentException ie) {
// ignore this
Log.w(TAG, ie);
}
}
}
createDialog中主要是创建各种Action,放入ArrayList,创建MyAdapter,最后创建ActionsDialog。Action封装了各自的事件处理。
ScreenshotAction
@Override
public void onPress() {
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
mScreenshotHelper.takeScreenshot(1, true, true, mHandler);//单击截屏
MetricsLogger.action(mContext,
MetricsEvent.ACTION_SCREENSHOT_POWER_MENU);
}
}, 500);
}
@Override
public boolean onLongPress() {
if (FeatureFlagUtils.isEnabled(mContext, FeatureFlagUtils.SCREENRECORD_LONG_PRESS)) {
mScreenRecordHelper.launchRecordPrompt();//长按录屏
} else {
onPress();
}
return true;
}
定义了单击和长按的处理,单击截屏,长按录屏
截屏
截屏通过ScreenshotHelper的takeScreenshot方法来实现
//ScreenshotHelper.java
public void takeScreenshot(final int screenshotType, final boolean hasStatus,
final boolean hasNav, @NonNull Handler handler) {
synchronized (mScreenshotLock) {
if (mScreenshotConnection != null) {
return;
}
final ComponentName serviceComponent = new ComponentName(SYSUI_PACKAGE,
SYSUI_SCREENSHOT_SERVICE);
final Intent serviceIntent = new Intent();
final Runnable mScreenshotTimeout = new Runnable() {//超时处理
@Override public void run() {
synchronized (mScreenshotLock) {
if (mScreenshotConnection != null) {
mContext.unbindService(mScreenshotConnection);
mScreenshotConnection = null;
notifyScreenshotError();
}
}
}
};
serviceIntent.setComponent(serviceComponent);
ServiceConnection conn = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
synchronized (mScreenshotLock) {
if (mScreenshotConnection != this) {
return;
}
Messenger messenger = new Messenger(service);
Message msg = Message.obtain(null, screenshotType);
final ServiceConnection myConn = this;
Handler h = new Handler(handler.getLooper()) {//截屏完成处理
@Override
public void handleMessage(Message msg) {
synchronized (mScreenshotLock) {
if (mScreenshotConnection == myConn) {
mContext.unbindService(mScreenshotConnection);
mScreenshotConnection = null;
handler.removeCallbacks(mScreenshotTimeout);
}
}
}
};
msg.replyTo = new Messenger(h);
msg.arg1 = hasStatus ? 1: 0;
msg.arg2 = hasNav ? 1: 0;
try {
messenger.send(msg);//通过Messenger发送消息开始截屏
} catch (RemoteException e) {
Log.e(TAG, "Couldn't take screenshot: " + e);
}
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
synchronized (mScreenshotLock) {
if (mScreenshotConnection != null) {//断开连接
mContext.unbindService(mScreenshotConnection);
mScreenshotConnection = null;
handler.removeCallbacks(mScreenshotTimeout);
notifyScreenshotError();
}
}
}
};
if (mContext.bindServiceAsUser(serviceIntent, conn,
Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE,
UserHandle.CURRENT)) {//绑定服务
mScreenshotConnection = conn;
handler.postDelayed(mScreenshotTimeout, SCREENSHOT_TIMEOUT_MS);//发送超时处理逻辑
}
}
}
以上代码绑定了TakeScreenshotService,然后通过Messenger来进行通信,发送Message来启动截屏,接收Message来处理截屏完成逻辑,并发送延时消息来处理超时逻辑。
//TakeScreenshotService.java
private static GlobalScreenshot mScreenshot;
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
final Messenger callback = msg.replyTo;
Runnable finisher = new Runnable() {//截屏完成处理
@Override
public void run() {
Message reply = Message.obtain(null, 1);
try {
callback.send(reply);//发送消息给请求者
} catch (RemoteException e) {
}
}
};
// If the storage for this user is locked, we have no place to store
// the screenshot, so skip taking it instead of showing a misleading
// animation and error notification.
if (!getSystemService(UserManager.class).isUserUnlocked()) {
Log.w(TAG, "Skipping screenshot because storage is locked!");
post(finisher);
return;
}
if (mScreenshot == null) {
mScreenshot = new GlobalScreenshot(TakeScreenshotService.this);
}
switch (msg.what) {
case WindowManager.TAKE_SCREENSHOT_FULLSCREEN:
mScreenshot.takeScreenshot(finisher, msg.arg1 > 0, msg.arg2 > 0);//截屏
break;
case WindowManager.TAKE_SCREENSHOT_SELECTED_REGION:
mScreenshot.takeScreenshotPartial(finisher, msg.arg1 > 0, msg.arg2 > 0);
break;
default:
Log.d(TAG, "Invalid screenshot option: " + msg.what);
}
}
};
@Override
public IBinder onBind(Intent intent) {
return new Messenger(mHandler).getBinder();//创建Messenger 与请求者通信
}
@Override
public boolean onUnbind(Intent intent) {
if (mScreenshot != null) mScreenshot.stopScreenshot();//解绑时停止截屏
return true;
}
TakeScreenshotService主要用于跨进程通信,接收截屏请求以及反馈截屏完成消息,将具体的截屏交由GlobalScreenshot来进行。
//GlobalScreenshot.java
private void takeScreenshot(Runnable finisher, boolean statusBarVisible, boolean navBarVisible,
Rect crop) {
int rot = mDisplay.getRotation();
int width = crop.width();
int height = crop.height();
// Take the screenshot
mScreenBitmap = SurfaceControl.screenshot(crop, width, height, rot);//截屏返回一个Bitmap
if (mScreenBitmap == null) {
notifyScreenshotError(mContext, mNotificationManager,
R.string.screenshot_failed_to_capture_text);//发送截屏异常通知
finisher.run();//截屏异常导致截屏结束 通知请求者
return;
}
// Optimizations
mScreenBitmap.setHasAlpha(false);
mScreenBitmap.prepareToDraw();
// Start the post-screenshot animation
startAnimation(finisher, mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels,
statusBarVisible, navBarVisible);//启动截屏动画
}
void takeScreenshot(Runnable finisher, boolean statusBarVisible, boolean navBarVisible) {
mDisplay.getRealMetrics(mDisplayMetrics);
takeScreenshot(finisher, statusBarVisible, navBarVisible,
new Rect(0, 0, mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels));
}
上面通过SurfaceControl截屏获取一个Bitmap对象,如果Bitmap是null,发送通知,截屏结束,否则显示截屏动画
private void startAnimation(final Runnable finisher, int w, int h, boolean statusBarVisible,
boolean navBarVisible) {
// If power save is on, show a toast so there is some visual indication that a screenshot
// has been taken.
PowerManager powerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
if (powerManager.isPowerSaveMode()) {
Toast.makeText(mContext, R.string.screenshot_saved_title, Toast.LENGTH_SHORT).show();
}
// Add the view for the animation
mScreenshotView.setImageBitmap(mScreenBitmap);//将截屏Bitmap显示在控件上
mScreenshotLayout.requestFocus();
// Setup the animation with the screenshot just taken
if (mScreenshotAnimation != null) {//重置Animation
if (mScreenshotAnimation.isStarted()) {
mScreenshotAnimation.end();
}
mScreenshotAnimation.removeAllListeners();
}
mWindowManager.addView(mScreenshotLayout, mWindowLayoutParams);//将控件显示出来
ValueAnimator screenshotDropInAnim = createScreenshotDropInAnimation();
ValueAnimator screenshotFadeOutAnim = createScreenshotDropOutAnimation(w, h,
statusBarVisible, navBarVisible);
mScreenshotAnimation = new AnimatorSet();
mScreenshotAnimation.playSequentially(screenshotDropInAnim, screenshotFadeOutAnim);
mScreenshotAnimation.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
// Save the screenshot once we have a bit of time now
saveScreenshotInWorkerThread(finisher);//显示完成 保存
mWindowManager.removeView(mScreenshotLayout);//移除控件
// Clear any references to the bitmap
mScreenBitmap = null;
mScreenshotView.setImageBitmap(null);
}
});
mScreenshotLayout.post(new Runnable() {
@Override
public void run() {
// Play the shutter sound to notify that we've taken a screenshot
mCameraSound.play(MediaActionSound.SHUTTER_CLICK);//播放声音
mScreenshotView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
mScreenshotView.buildLayer();
mScreenshotAnimation.start();//启动动画
}
});
}
以上代码将Bitmap显示在界面上,播放声音,启动动画,动画完成进行Bitmap保存。
private void saveScreenshotInWorkerThread(Runnable finisher) {
SaveImageInBackgroundData data = new SaveImageInBackgroundData();//创建对象 存放数据
data.context = mContext;
data.image = mScreenBitmap;
data.iconSize = mNotificationIconSize;
data.finisher = finisher;
data.previewWidth = mPreviewWidth;
data.previewheight = mPreviewHeight;
if (mSaveInBgTask != null) {
mSaveInBgTask.cancel(false);
}
mSaveInBgTask = new SaveImageInBackgroundTask(mContext, data, mNotificationManager)
.execute();//启动异步任务保存
}
SaveImageInBackgroundTask主要功能是保存Bitmap,并进行通知显示,任务开始即显示保存中的通知,结束显示带分享、删除、编辑的通知,另外调用finisher通知截屏完成,当点击删除时,通过DeleteScreenshotReceiver接收广播,由DeleteImageInBackgroundTask执行删除逻辑。
录屏
录屏通过ScreenRecordHelper的launchRecordPrompt方法来实现
//ScreenRecordHelper.java
public void launchRecordPrompt() {
final ComponentName launcherComponent = new ComponentName(SYSUI_PACKAGE,
SYSUI_SCREENRECORD_LAUNCHER);
final Intent intent = new Intent();
intent.setComponent(launcherComponent);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
mContext.startActivity(intent);//启动ScreenRecordDialog
}
ScreenRecordHelper直接启动了ScreenRecordDialog
//ScreenRecordDialog.java
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.screen_record_dialog);
final CheckBox micCheckBox = findViewById(R.id.checkbox_mic);//是否录音勾选框
final CheckBox tapsCheckBox = findViewById(R.id.checkbox_taps);//是否显示点击勾选框
final Button recordButton = findViewById(R.id.record_button);//开始录屏按钮
recordButton.setOnClickListener(v -> {
mUseAudio = micCheckBox.isChecked();
mShowTaps = tapsCheckBox.isChecked();
Log.d(TAG, "Record button clicked: audio " + mUseAudio + ", taps " + mShowTaps);
if (mUseAudio && checkSelfPermission(Manifest.permission.RECORD_AUDIO)
!= PackageManager.PERMISSION_GRANTED) {
Log.d(TAG, "Requesting permission for audio");
requestPermissions(new String[]{Manifest.permission.RECORD_AUDIO},
REQUEST_CODE_PERMISSIONS_AUDIO);//请求权限
} else {
requestScreenCapture();//请求录屏
}
});
}
private void requestScreenCapture() {
MediaProjectionManager mediaProjectionManager = (MediaProjectionManager) getSystemService(
Context.MEDIA_PROJECTION_SERVICE);
Intent permissionIntent = mediaProjectionManager.createScreenCaptureIntent();
//启动MediaProjectionPermissionActivity获取权限
if (mUseAudio) {
startActivityForResult(permissionIntent,
mShowTaps ? REQUEST_CODE_VIDEO_AUDIO_TAPS : REQUEST_CODE_VIDEO_AUDIO);
} else {
startActivityForResult(permissionIntent,
mShowTaps ? REQUEST_CODE_VIDEO_TAPS : REQUEST_CODE_VIDEO_ONLY);
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
mShowTaps = (requestCode == REQUEST_CODE_VIDEO_TAPS
|| requestCode == REQUEST_CODE_VIDEO_AUDIO_TAPS);
switch (requestCode) {
case REQUEST_CODE_VIDEO_TAPS:
case REQUEST_CODE_VIDEO_AUDIO_TAPS:
case REQUEST_CODE_VIDEO_ONLY:
case REQUEST_CODE_VIDEO_AUDIO:
if (resultCode == RESULT_OK) {
mUseAudio = (requestCode == REQUEST_CODE_VIDEO_AUDIO
|| requestCode == REQUEST_CODE_VIDEO_AUDIO_TAPS);
startForegroundService(
RecordingService.getStartIntent(this, resultCode, data, mUseAudio,
mShowTaps));//启动RecordingService开始录屏
} else {
Toast.makeText(this,
getResources().getString(R.string.screenrecord_permission_error),
Toast.LENGTH_SHORT).show();//显示权限异常信息
}
finish();
break;
case REQUEST_CODE_PERMISSIONS:
int permission = checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE);
if (permission != PackageManager.PERMISSION_GRANTED) {
Toast.makeText(this,
getResources().getString(R.string.screenrecord_permission_error),
Toast.LENGTH_SHORT).show();//显示权限异常信息
finish();
} else {
requestScreenCapture();
}
break;
case REQUEST_CODE_PERMISSIONS_AUDIO:
int videoPermission = checkSelfPermission(
Manifest.permission.WRITE_EXTERNAL_STORAGE);
int audioPermission = checkSelfPermission(Manifest.permission.RECORD_AUDIO);
if (videoPermission != PackageManager.PERMISSION_GRANTED
|| audioPermission != PackageManager.PERMISSION_GRANTED) {
Toast.makeText(this,
getResources().getString(R.string.screenrecord_permission_error),
Toast.LENGTH_SHORT).show();//显示权限异常信息
finish();
} else {
requestScreenCapture();
}
break;
}
}
ScreenRecordDialog主要进行权限检查,然后启动RecordingService来进行录屏
//RecordingService
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d(TAG, "RecordingService is starting");
if (intent == null) {
return Service.START_NOT_STICKY;
}
String action = intent.getAction();
NotificationManager notificationManager =
(NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
switch (action) {
case ACTION_START://启动录屏
int resultCode = intent.getIntExtra(EXTRA_RESULT_CODE, Activity.RESULT_CANCELED);
mUseAudio = intent.getBooleanExtra(EXTRA_USE_AUDIO, false);
mShowTaps = intent.getBooleanExtra(EXTRA_SHOW_TAPS, false);
Intent data = intent.getParcelableExtra(EXTRA_DATA);
if (data != null) {
mMediaProjection = mMediaProjectionManager.getMediaProjection(resultCode, data);
startRecording();//开始录屏
}
break;
case ACTION_CANCEL://取消录屏
stopRecording();//停止录屏
// Delete temp file
if (!mTempFile.delete()) {//删除文件
Log.e(TAG, "Error canceling screen recording!");
Toast.makeText(this, R.string.screenrecord_delete_error, Toast.LENGTH_LONG)
.show();
} else {
Toast.makeText(this, R.string.screenrecord_cancel_success, Toast.LENGTH_LONG)
.show();
}
// Close quick shade
sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
break;
case ACTION_STOP://停止录屏
stopRecording();//停止录屏
// Move temp file to user directory
File recordDir = new File(
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES),
RECORD_DIR);
recordDir.mkdirs();//创建目录
String fileName = new SimpleDateFormat("'screen-'yyyyMMdd-HHmmss'.mp4'")
.format(new Date());
Path path = new File(recordDir, fileName).toPath();
try {
Files.move(mTempFile.toPath(), path);//移动文件
Notification notification = createSaveNotification(path);
notificationManager.notify(NOTIFICATION_ID, notification);//通知
} catch (IOException e) {
e.printStackTrace();
Toast.makeText(this, R.string.screenrecord_delete_error, Toast.LENGTH_LONG)
.show();
}
break;
case ACTION_PAUSE://暂停
mMediaRecorder.pause();
setNotificationActions(true, notificationManager);
break;
case ACTION_RESUME://恢复
mMediaRecorder.resume();
setNotificationActions(false, notificationManager);
break;
case ACTION_SHARE://分享
File shareFile = new File(intent.getStringExtra(EXTRA_PATH));
Uri shareUri = FileProvider.getUriForFile(this, FILE_PROVIDER, shareFile);
Intent shareIntent = new Intent(Intent.ACTION_SEND)
.setType("video/mp4")
.putExtra(Intent.EXTRA_STREAM, shareUri);
String shareLabel = getResources().getString(R.string.screenrecord_share_label);
// Close quick shade
sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
// Remove notification
notificationManager.cancel(NOTIFICATION_ID);
startActivity(Intent.createChooser(shareIntent, shareLabel)
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
break;
case ACTION_DELETE://删除
// Close quick shade
sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
File file = new File(intent.getStringExtra(EXTRA_PATH));
if (file.delete()) {//删除文件
Toast.makeText(
this,
R.string.screenrecord_delete_description,
Toast.LENGTH_LONG).show();
// Remove notification
notificationManager.cancel(NOTIFICATION_ID);
} else {
Log.e(TAG, "Error deleting screen recording!");
Toast.makeText(this, R.string.screenrecord_delete_error, Toast.LENGTH_LONG)
.show();
}
break;
}
return Service.START_STICKY;
}
RecordingService主要接收外部请求来控制录屏,包括开始、结束、取消、暂停、恢复、分享、删除,其中开始录屏调用的startRecording方法。
private void startRecording() {
try {
mTempFile = File.createTempFile("temp", ".mp4");//创建临时文件
Log.d(TAG, "Writing video output to: " + mTempFile.getAbsolutePath());
setTapsVisible(mShowTaps);//设置点击可见性
// Set up media recorder
mMediaRecorder = new MediaRecorder();//创建MediaRecorder并进行初始化
if (mUseAudio) {
mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
}
mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);
mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
// Set up video
DisplayMetrics metrics = getResources().getDisplayMetrics();
int screenWidth = metrics.widthPixels;
int screenHeight = metrics.heightPixels;
mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
mMediaRecorder.setVideoSize(screenWidth, screenHeight);
mMediaRecorder.setVideoFrameRate(VIDEO_FRAME_RATE);
mMediaRecorder.setVideoEncodingBitRate(VIDEO_BIT_RATE);
// Set up audio
if (mUseAudio) {
mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
mMediaRecorder.setAudioChannels(TOTAL_NUM_TRACKS);
mMediaRecorder.setAudioEncodingBitRate(AUDIO_BIT_RATE);
mMediaRecorder.setAudioSamplingRate(AUDIO_SAMPLE_RATE);
}
mMediaRecorder.setOutputFile(mTempFile);
mMediaRecorder.prepare();
// Create surface
mInputSurface = mMediaRecorder.getSurface();
mVirtualDisplay = mMediaProjection.createVirtualDisplay(
"Recording Display",
screenWidth,
screenHeight,
metrics.densityDpi,
DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,
mInputSurface,
null,
null);
mMediaRecorder.start();//开始录制
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
createRecordingNotification();//显示通知 通知上显示停止、暂停、继续、取消等操作
}
PowerAction
@Override
public void onPress() {
// shutdown by making sure radio and power are handled accordingly.
mWindowManagerFuncs.shutdown();//mWindowManagerFuncs即为GlobalActionsComponent
}
从GlobalActionsComponent的GlobalActionsManager实现可知,调用了IStatusBarService的shutdown方法,也就是调用了StatusBarManagerService的shutdown方法
@Override
public void shutdown() {
enforceStatusBarService();
long identity = Binder.clearCallingIdentity();
try {
// ShutdownThread displays UI, so give it a UI context.
mHandler.post(() ->
ShutdownThread.shutdown(getUiContext(),
PowerManager.SHUTDOWN_USER_REQUESTED, false));
} finally {
Binder.restoreCallingIdentity(identity);
}
}
上面的代码在主线程调用了ShutdownThread的shutdown方法
public static void shutdown(final Context context, String reason, boolean confirm) {
mReboot = false;
mRebootSafeMode = false;
mReason = reason;
shutdownInner(context, confirm);
}
private static void shutdownInner(final Context context, boolean confirm) {
// ShutdownThread is called from many places, so best to verify here that the context passed
// in is themed.
context.assertRuntimeOverlayThemable();
// ensure that only one thread is trying to power down.
// any additional calls are just returned
synchronized (sIsStartedGuard) {
if (sIsStarted) {
Log.d(TAG, "Request to shutdown already running, returning.");
return;
}
}
final int longPressBehavior = context.getResources().getInteger(
com.android.internal.R.integer.config_longPressOnPowerBehavior);
final int resourceId = mRebootSafeMode
? com.android.internal.R.string.reboot_safemode_confirm
: (longPressBehavior == 2
? com.android.internal.R.string.shutdown_confirm_question
: com.android.internal.R.string.shutdown_confirm);
Log.d(TAG, "Notifying thread to start shutdown longPressBehavior=" + longPressBehavior);
if (confirm) {
final CloseDialogReceiver closer = new CloseDialogReceiver(context);
if (sConfirmDialog != null) {
sConfirmDialog.dismiss();
}
sConfirmDialog = new AlertDialog.Builder(context)
.setTitle(mRebootSafeMode
? com.android.internal.R.string.reboot_safemode_title
: com.android.internal.R.string.power_off)
.setMessage(resourceId)
.setPositiveButton(com.android.internal.R.string.yes, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
beginShutdownSequence(context);
}
})
.setNegativeButton(com.android.internal.R.string.no, null)
.create();
closer.dialog = sConfirmDialog;
sConfirmDialog.setOnDismissListener(closer);
sConfirmDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
sConfirmDialog.show();
} else {
beginShutdownSequence(context);
}
}
这里不管是否显示对话框,最终都调用了beginShutdownSequence方法
private static void beginShutdownSequence(Context context) {
synchronized (sIsStartedGuard) {
if (sIsStarted) {
Log.d(TAG, "Shutdown sequence already running, returning.");
return;
}
sIsStarted = true;
}
sInstance.mProgressDialog = showShutdownDialog(context);//显示关机对话框
sInstance.mContext = context;
sInstance.mPowerManager = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
// make sure we never fall asleep again
sInstance.mCpuWakeLock = null;
try {
sInstance.mCpuWakeLock = sInstance.mPowerManager.newWakeLock(
PowerManager.PARTIAL_WAKE_LOCK, TAG + "-cpu");
sInstance.mCpuWakeLock.setReferenceCounted(false);
sInstance.mCpuWakeLock.acquire();
} catch (SecurityException e) {
Log.w(TAG, "No permission to acquire wake lock", e);
sInstance.mCpuWakeLock = null;
}
// also make sure the screen stays on for better user experience
sInstance.mScreenWakeLock = null;
if (sInstance.mPowerManager.isScreenOn()) {
try {
sInstance.mScreenWakeLock = sInstance.mPowerManager.newWakeLock(
PowerManager.FULL_WAKE_LOCK, TAG + "-screen");
sInstance.mScreenWakeLock.setReferenceCounted(false);
sInstance.mScreenWakeLock.acquire();
} catch (SecurityException e) {
Log.w(TAG, "No permission to acquire wake lock", e);
sInstance.mScreenWakeLock = null;
}
}
if (SecurityLog.isLoggingEnabled()) {
SecurityLog.writeEvent(SecurityLog.TAG_OS_SHUTDOWN);
}
// start the thread that initiates shutdown
sInstance.mHandler = new Handler() {
};
sInstance.start();//启动线程 处理关机逻辑
}
beginShutdownSequence中调用showShutdownDialog显示关机对话框,然后启动了ShutdownThread处理关机逻辑
private static ProgressDialog showShutdownDialog(Context context) {
// Throw up a system dialog to indicate the device is rebooting / shutting down.
ProgressDialog pd = new ProgressDialog(context);
// Path 1: Reboot to recovery for update
// Condition: mReason startswith REBOOT_RECOVERY_UPDATE
//
// Path 1a: uncrypt needed
// Condition: if /cache/recovery/uncrypt_file exists but
// /cache/recovery/block.map doesn't.
// UI: determinate progress bar (mRebootHasProgressBar == True)
//
// * Path 1a is expected to be removed once the GmsCore shipped on
// device always calls uncrypt prior to reboot.
//
// Path 1b: uncrypt already done
// UI: spinning circle only (no progress bar)
//
// Path 2: Reboot to recovery for factory reset
// Condition: mReason == REBOOT_RECOVERY
// UI: spinning circle only (no progress bar)
//
// Path 3: Regular reboot / shutdown
// Condition: Otherwise
// UI: spinning circle only (no progress bar)
// mReason could be "recovery-update" or "recovery-update,quiescent".
if (mReason != null && mReason.startsWith(PowerManager.REBOOT_RECOVERY_UPDATE)) {
// We need the progress bar if uncrypt will be invoked during the
// reboot, which might be time-consuming.
mRebootHasProgressBar = RecoverySystem.UNCRYPT_PACKAGE_FILE.exists()
&& !(RecoverySystem.BLOCK_MAP_FILE.exists());
pd.setTitle(context.getText(com.android.internal.R.string.reboot_to_update_title));
if (mRebootHasProgressBar) {
pd.setMax(100);
pd.setProgress(0);
pd.setIndeterminate(false);
pd.setProgressNumberFormat(null);
pd.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
pd.setMessage(context.getText(
com.android.internal.R.string.reboot_to_update_prepare));
} else {
if (showSysuiReboot()) {
return null;
}
pd.setIndeterminate(true);
pd.setMessage(context.getText(
com.android.internal.R.string.reboot_to_update_reboot));
}
} else if (mReason != null && mReason.equals(PowerManager.REBOOT_RECOVERY)) {
if (RescueParty.isAttemptingFactoryReset()) {
// We're not actually doing a factory reset yet; we're rebooting
// to ask the user if they'd like to reset, so give them a less
// scary dialog message.
pd.setTitle(context.getText(com.android.internal.R.string.power_off));
pd.setMessage(context.getText(com.android.internal.R.string.shutdown_progress));
pd.setIndeterminate(true);
} else {
// Factory reset path. Set the dialog message accordingly.
pd.setTitle(context.getText(com.android.internal.R.string.reboot_to_reset_title));
pd.setMessage(context.getText(
com.android.internal.R.string.reboot_to_reset_message));
pd.setIndeterminate(true);
}
} else {
if (showSysuiReboot()) {//显示SystemUI中的界面
return null;
}
pd.setTitle(context.getText(com.android.internal.R.string.power_off));
pd.setMessage(context.getText(com.android.internal.R.string.shutdown_progress));
pd.setIndeterminate(true);
}
pd.setCancelable(false);
pd.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
pd.show();
return pd;
}
正常关机时会调用showSysuiReboot方法显示关机界面
private static boolean showSysuiReboot() {
Log.d(TAG, "Attempting to use SysUI shutdown UI");
try {
StatusBarManagerInternal service = LocalServices.getService(
StatusBarManagerInternal.class);
if (service.showShutdownUi(mReboot, mReason)) {
// Sysui will handle shutdown UI.
Log.d(TAG, "SysUI handling shutdown UI");
return true;
}
} catch (Exception e) {
// If anything went wrong, ignore it and use fallback ui
}
Log.d(TAG, "SysUI is unavailable");
return false;
}
上面调用了StatusBarManagerInternal的showShutdownUi方法,StatusBarManagerInternal的实现在StatusBarManagerService中
@Override
public boolean showShutdownUi(boolean isReboot, String reason) {
if (!mContext.getResources().getBoolean(R.bool.config_showSysuiShutdown)) {
return false;
}
if (mBar != null) {
try {
mBar.showShutdownUi(isReboot, reason);//mBar为IStatusBar对象,即为CommandQueue
return true;
} catch (RemoteException ex) {}
}
return false;
}
这里调用了CommandQueue的showShutdownUi方法
@Override
public void showShutdownUi(boolean isReboot, String reason) {
synchronized (mLock) {
mHandler.removeMessages(MSG_SHOW_SHUTDOWN_UI);
mHandler.obtainMessage(MSG_SHOW_SHUTDOWN_UI, isReboot ? 1 : 0, 0, reason)
.sendToTarget();
}
}
case MSG_SHOW_SHUTDOWN_UI:
for (int i = 0; i < mCallbacks.size(); i++) {
mCallbacks.get(i).handleShowShutdownUi(msg.arg1 != 0, (String) msg.obj);
}
break;
CommandQueue中在主线程调用了Callbacks的handleShowShutdownUi方法,这个方法就在GlobalActionsComponent中被实现,调用了GlobalActionsImpl的showShutdwonUi方法。
//GlobalActionsImpl.java
@Override
public void showShutdownUi(boolean isReboot, String reason) {
ScrimDrawable background = new ScrimDrawable();
background.setAlpha((int) (SHUTDOWN_SCRIM_ALPHA * 255));
Dialog d = new Dialog(mContext,
com.android.systemui.R.style.Theme_SystemUI_Dialog_GlobalActions);//创建Dialog
// Window initialization 初始化Dialog的Window
Window window = d.getWindow();
window.requestFeature(Window.FEATURE_NO_TITLE);
window.getAttributes().systemUiVisibility |= View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| View.SYSTEM_UI_FLAG_LAYOUT_STABLE
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION;
// Inflate the decor view, so the attributes below are not overwritten by the theme.
window.getDecorView();
window.getAttributes().width = ViewGroup.LayoutParams.MATCH_PARENT;
window.getAttributes().height = ViewGroup.LayoutParams.MATCH_PARENT;
window.getAttributes().layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
window.setType(WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY);
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.setBackgroundDrawable(background);
window.setWindowAnimations(R.style.Animation_Toast);
d.setContentView(R.layout.shutdown_dialog);//设置Dialog的View
d.setCancelable(false);
int color = Utils.getColorAttrDefaultColor(mContext,
com.android.systemui.R.attr.wallpaperTextColor);
boolean onKeyguard = mContext.getSystemService(
KeyguardManager.class).isKeyguardLocked();
ProgressBar bar = d.findViewById(R.id.progress);//进度条
bar.getIndeterminateDrawable().setTint(color);
TextView message = d.findViewById(R.id.text1);//文本控件
message.setTextColor(color);
if (isReboot) message.setText(R.string.reboot_to_reset_message);
GradientColors colors = Dependency.get(SysuiColorExtractor.class).getNeutralColors();
background.setColor(colors.getMainColor(), false);
d.show();//显示对话框
}
关机界面很简单,就是一个Dialog,包含一个ProgressBar和一个TextView