通常未通过特殊定制的 Android 系统,截屏都是经过同时按住音量下键和电源键来截屏。本篇文章就只讨论使用这些特殊按键来进行截屏。
这里我们就要明白事件是在哪里进行分发拦截的。通过源码的分析,我们发现是在PhoneWindowManager.java 中。
PhoneWindowManager#interceptKeyBeforeQueueing()
frameworks/base/services/core/java/com/android/server/policy/PhoneWindowManager.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| @Override public int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags) { if (!mSystemBooted) { return 0; } switch (keyCode) { ...... case KeyEvent.KEYCODE_VOLUME_DOWN: case KeyEvent.KEYCODE_VOLUME_UP: case KeyEvent.KEYCODE_VOLUME_MUTE: { handleVolumeKey(event, policyFlags); ...... break; } ...... case KeyEvent.KEYCODE_POWER: { ...... if (down) { interceptPowerKeyDown(event, interactive); } else { interceptPowerKeyUp(event, interactive, canceled); } break; } } return result; }
|
1、电源键处理
PhoneWindowManager#interceptPowerKeyDown()
frameworks/base/services/core/java/com/android/server/policy/PhoneWindowManager.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| private void interceptPowerKeyDown(KeyEvent event, boolean interactive) { if (interactive && !mScreenshotChordPowerKeyTriggered && (event.getFlags() & KeyEvent.FLAG_FALLBACK) == 0) { mScreenshotChordPowerKeyTriggered = true; mScreenshotChordPowerKeyTime = event.getDownTime(); interceptScreenshotChord(); interceptRingerToggleChord(); } }
|
interceptScreenshotChord()该方法下面再说,先介绍电源按键、音量按键的处理。
2、音量键处理
PhoneWindowManager#handleVolumeKey()
frameworks/base/services/core/java/com/android/server/policy/PhoneWindowManager.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| public void handleVolumeKey(KeyEvent event, int policyFlags) { final boolean down = event.getAction() == KeyEvent.ACTION_DOWN; final int keyCode = event.getKeyCode(); final boolean interactive = (policyFlags & FLAG_INTERACTIVE) != 0; if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) { if (down) { cancelPendingRingerToggleChordAction(); if (interactive && !mScreenshotChordVolumeDownKeyTriggered && (event.getFlags() & KeyEvent.FLAG_FALLBACK) == 0) { mScreenshotChordVolumeDownKeyTriggered = true; mScreenshotChordVolumeDownKeyTime = event.getDownTime(); mScreenshotChordVolumeDownKeyConsumed = false; cancelPendingPowerKeyAction(); interceptScreenshotChord(); interceptAccessibilityShortcutChord(); } } else { } } else if (keyCode == KeyEvent.KEYCODE_VOLUME_UP) { } return; }
|
3、截屏事件处理 interceptScreenshotChord()
PhoneWindowManager#interceptScreenshotChord()
frameworks/base/services/core/java/com/android/server/policy/PhoneWindowManager.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| private void interceptScreenshotChord() {
if (mScreenshotChordEnabled && mScreenshotChordVolumeDownKeyTriggered && mScreenshotChordPowerKeyTriggered && !mA11yShortcutChordVolumeUpKeyTriggered) { final long now = SystemClock.uptimeMillis(); if (now <= mScreenshotChordVolumeDownKeyTime + SCREENSHOT_CHORD_DEBOUNCE_DELAY_MILLIS && now <= mScreenshotChordPowerKeyTime + SCREENSHOT_CHORD_DEBOUNCE_DELAY_MILLIS) { boolean inLongScreenshot = Settings.System.getIntForUser(mContext.getContentResolver(), LONGSCREENSHOT_SETTING, 0, UserHandle.USER_CURRENT_OR_SELF) == 1; if (hasInPowerUtrlSavingMode() || inLongScreenshot) { return; } mScreenshotChordVolumeDownKeyConsumed = true; cancelPendingPowerKeyAction(); mScreenshotRunnable.setScreenshotType(TAKE_SCREENSHOT_FULLSCREEN); mScreenshotRunnable.setScreenshotSource(SCREENSHOT_KEY_CHORD); mHandler.postDelayed(mScreenshotRunnable, getScreenshotChordLongPressDelay()); } } }
|
继续查看ScreenshotRunnable,此时会一步步向下调用,最终到SystemUI。
frameworks/base/services/core/java/com/android/server/policy/PhoneWindowManager.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| private class ScreenshotRunnable implements Runnable { private int mScreenshotType = TAKE_SCREENSHOT_FULLSCREEN; private int mScreenshotSource = SCREENSHOT_KEY_OTHER; public void setScreenshotType(int screenshotType) { mScreenshotType = screenshotType; } public void setScreenshotSource(int screenshotSource) { mScreenshotSource = screenshotSource; } @Override public void run() { mDefaultDisplayPolicy.takeScreenshot(mScreenshotType, mScreenshotSource); } }
|
DisplayPolicy#takeScreenshot()
frameworks/base/services/core/java/com/android/server/wm/DisplayPolicy.java
1 2 3 4 5 6 7 8 9
| public void takeScreenshot(int screenshotType, int source) { if (mScreenshotHelper != null) { mScreenshotHelper.takeScreenshot(screenshotType, mStatusBar != null && mStatusBar.isVisibleLw(), mNavigationBar != null && mNavigationBar.isVisibleLw(), source, mHandler, null ); } }
|
继续往下看ScreenshotHelper#takeScreenshot()
frameworks/base/core/java/com/android/internal/util/ScreenshotHelper.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112
| public void takeScreenshot(final int screenshotType, final boolean hasStatus, final boolean hasNav, int source, @NonNull Handler handler, @Nullable Consumer<Uri> completionConsumer) { ScreenshotRequest screenshotRequest = new ScreenshotRequest(source, hasStatus, hasNav); takeScreenshot(screenshotType, SCREENSHOT_TIMEOUT_MS, handler, screenshotRequest, completionConsumer); }
private void takeScreenshot(final int screenshotType, long timeoutMs, @NonNull Handler handler, ScreenshotRequest screenshotRequest, @Nullable Consumer<Uri> completionConsumer) { synchronized (mScreenshotLock) { final Runnable mScreenshotTimeout = () -> { synchronized (mScreenshotLock) { if (mScreenshotConnection != null) { Log.e(TAG, "Timed out before getting screenshot capture response"); resetConnection(); notifyScreenshotError(); } } if (completionConsumer != null) { completionConsumer.accept(null); } }; Message msg = Message.obtain(null, screenshotType, screenshotRequest); Handler h = new Handler(handler.getLooper()) { @Override public void handleMessage(Message msg) { switch (msg.what) { case SCREENSHOT_MSG_URI: if (completionConsumer != null) { completionConsumer.accept((Uri) msg.obj); } handler.removeCallbacks(mScreenshotTimeout); break; case SCREENSHOT_MSG_PROCESS_COMPLETE: synchronized (mScreenshotLock) { resetConnection(); } break; } } }; msg.replyTo = new Messenger(h); if (mScreenshotConnection == null || mScreenshotService == null) { final ComponentName serviceComponent = ComponentName.unflattenFromString( mContext.getResources().getString( com.android.internal.R.string.config_screenshotServiceComponent)); final Intent serviceIntent = new Intent(); serviceIntent.setComponent(serviceComponent); ServiceConnection conn = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { synchronized (mScreenshotLock) { if (mScreenshotConnection != this) { return; } mScreenshotService = service; Messenger messenger = new Messenger(mScreenshotService); try { messenger.send(msg); } catch (RemoteException e) { Log.e(TAG, "Couldn't take screenshot: " + e); if (completionConsumer != null) { completionConsumer.accept(null); } } } } @Override public void onServiceDisconnected(ComponentName name) { synchronized (mScreenshotLock) { if (mScreenshotConnection != null) { resetConnection(); if (handler.hasCallbacks(mScreenshotTimeout)) { handler.removeCallbacks(mScreenshotTimeout); notifyScreenshotError(); } } } } }; if (mContext.bindServiceAsUser(serviceIntent, conn, Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE, UserHandle.CURRENT)) { mScreenshotConnection = conn; handler.postDelayed(mScreenshotTimeout, timeoutMs); } } else { Messenger messenger = new Messenger(mScreenshotService); try { messenger.send(msg); } catch (RemoteException e) { Log.e(TAG, "Couldn't take screenshot: " + e); if (completionConsumer != null) { completionConsumer.accept(null); } } handler.postDelayed(mScreenshotTimeout, timeoutMs); } } }
|
客户端通过向服务端发送 message 来将截屏任务交给 service,由 service 处理后面的操作。
frameworks/base/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
| private Handler mHandler = new Handler(Looper.myLooper()) { @Override public void handleMessage(Message msg) { final Messenger callback = msg.replyTo; Consumer<Uri> uriConsumer = uri -> { Message reply = Message.obtain(null, SCREENSHOT_MSG_URI, uri); try { / /Messenger 双向通信,在服务端用远程客户端的 Messenger 对象给客户端发送信息 callback.send(reply); } catch (RemoteException e) { } }; Runnable onComplete = () -> { Message reply = Message.obtain(null, SCREENSHOT_MSG_PROCESS_COMPLETE); try { callback.send(reply); } catch (RemoteException e) { } };
if (!mUserManager.isUserUnlocked()) { Log.w(TAG, "Skipping screenshot because storage is locked!"); post(() -> uriConsumer.accept(null)); post(onComplete); return; } ScreenshotHelper.ScreenshotRequest screenshotRequest = (ScreenshotHelper.ScreenshotRequest) msg.obj; mUiEventLogger.log(ScreenshotEvent.getScreenshotSource(screenshotRequest.getSource())); switch (msg.what) { case WindowManager.TAKE_SCREENSHOT_FULLSCREEN: mScreenshot.takeScreenshot(uriConsumer, onComplete); break; case WindowManager.TAKE_SCREENSHOT_SELECTED_REGION: mScreenshot.takeScreenshot(uriConsumer, onComplete); break; case WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE: Bitmap screenshot = BitmapUtil.bundleToHardwareBitmap( screenshotRequest.getBitmapBundle()); Rect screenBounds = screenshotRequest.getBoundsInScreen(); Insets insets = screenshotRequest.getInsets(); int taskId = screenshotRequest.getTaskId(); int userId = screenshotRequest.getUserId(); ComponentName topComponent = screenshotRequest.getTopComponent(); mScreenshot.handleImageAsScreenshot(screenshot, screenBounds, insets, taskId, userId, topComponent, uriConsumer, onComplete); break; default: Log.d(TAG, "Invalid screenshot option: " + msg.what); } } };
|
TakeScreenshotService调用GlobalScreenshot.java的takeScreenshot();
GlobalScreenshot#takeScreenshot()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120
|
private void takeScreenshot(Consumer<Uri> finisher, Rect crop) { Rect screenRect = new Rect(crop); int rot = mDisplay.getRotation(); int width = crop.width(); int height = crop.height(); takeScreenshot(SurfaceControl.screenshot(crop, width, height, rot), finisher, screenRect, Insets.NONE, true); } private void takeScreenshot(Bitmap screenshot, Consumer<Uri> finisher, Rect screenRect, Insets screenInsets, boolean showFlash) { dismissScreenshot("new screenshot requested", true); mScreenBitmap = screenshot; if (mScreenBitmap == null) { mNotificationsController.notifyScreenshotError( R.string.screenshot_failed_to_capture_text); finisher.accept(null); mOnCompleteRunnable.run(); return; } if (!isUserSetupComplete()) { saveScreenshotAndToast(finisher); return; } mScreenBitmap.setHasAlpha(false); mScreenBitmap.prepareToDraw(); onConfigChanged(mContext.getResources().getConfiguration()); if (mDismissAnimation != null && mDismissAnimation.isRunning()) { mDismissAnimation.cancel(); } setWindowFocusable(true); startAnimation(finisher, screenRect, screenInsets, showFlash); }
private void startAnimation(final Consumer<Uri> finisher, Rect screenRect, Insets screenInsets, boolean showFlash) { if (mScreenshotIng == false) { mScreenshotIng = true; PowerManager powerManager =(PowerManager) mContext . getSystemService (Context.POWER_SERVICE); if (powerManager.isPowerSaveMode()) { Toast.makeText(mContext, R.string.screenshot_saved_title, Toast.LENGTH_SHORT).show(); } mScreenshotHandler.post(() -> { if (!mScreenshotLayout.isAttachedToWindow()) { mWindowManager.addView(mScreenshotLayout, mWindowLayoutParams); } mScreenshotAnimatedView.setImageDrawable( createScreenDrawable(mScreenBitmap, screenInsets)); setAnimatedViewSize(screenRect.width(), screenRect.height()); mScreenshotAnimatedView.setVisibility(View.GONE); mScreenshotPreview.setImageDrawable(createScreenDrawable(mScreenBitmap, screenInsets)); mScreenshotPreview.setVisibility(View.INVISIBLE); mScreenshotHandler.post(() -> { mScreenshotLayout.getViewTreeObserver().addOnComputeInternalInsetsListener(this); mScreenshotAnimation = createScreenshotDropInAnimation(screenRect, showFlash); saveScreenshotInWorkerThread(finisher, new ActionsReadyListener () { @Override void onActionsReady (SavedImageData imageData) { showUiOnActionsReady(imageData); mScreenshotIng = false; } }); mCameraSound.play(MediaActionSound.SHUTTER_CLICK); if (mScreenshotPreview.isAttachedToWindow()) { mScreenshotPreview.setLayerType(View.LAYER_TYPE_HARDWARE, null); mScreenshotPreview.buildLayer(); } mScreenshotAnimation.start(); }); }); } }
private void saveScreenshotInWorkerThread( Consumer<Uri> finisher, @Nullable ActionsReadyListener actionsReadyListener) { SaveImageInBackgroundData data = new SaveImageInBackgroundData(); data.image = mScreenBitmap; data.finisher = finisher; data.mActionsReadyListener = actionsReadyListener; if (mSaveInBgTask != null) { mSaveInBgTask.setActionsReadyListener(new ActionsReadyListener() { @Override void onActionsReady(SavedImageData imageData) { logSuccessOnActionsReady(imageData); } }); } mSaveInBgTask = new SaveImageInBackgroundTask(mContext, data); mSaveInBgTask.execute(); }
|
到此截屏流程完毕,可以查看下截图的View的xml文件:global_screenshot.xml
frameworks/base/packages/SystemUI/res/layout/global_screenshot.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/global_screenshot_frame" android:layout_width="match_parent" android:layout_height="match_parent"> <ImageView android:id="@+id/global_screenshot_actions_background" android:layout_height="@dimen/screenshot_bg_protection_height" android:layout_width="match_parent" android:layout_gravity="bottom" android:alpha="0.0" android:src="@drawable/screenshot_actions_background_protection"/> <!--截屏动画相关的View --> <ImageView android:id="@+id/global_screenshot_animated_view" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="top|start" android:visibility="gone" android:elevation="@dimen/screenshot_preview_elevation" android:background="@drawable/screenshot_rounded_corners" /> <ImageView android:id="@+id/global_screenshot_flash" android:layout_width="match_parent" android:layout_height="match_parent" android:visibility="gone" android:elevation="@dimen/screenshot_preview_elevation" android:src="@android:color/white"/> <com.android.systemui.screenshot.ScreenshotSelectorView android:id="@+id/global_screenshot_selector" android:layout_width="match_parent" android:layout_height="match_parent" android:visibility="gone" android:pointerIcon="crosshair"/> <!-- 此处包含了一个layout, 而缩略图的View就在此layout中, 截屏右上角的关闭缩略图按钮 也在此layout中 --> <include layout="@layout/global_screenshot_static"/> </FrameLayout>
|
本文链接:
http://longzhiye.top/2023/12/16/2023-12-16/