Loading packages/SystemUI/res/layout/long_screenshot.xml +19 −0 Original line number Diff line number Diff line Loading @@ -39,6 +39,25 @@ tools:minHeight="100dp" tools:minWidth="100dp" /> <com.android.systemui.screenshot.CropView android:id="@+id/crop_view" android:visibility="gone" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginVertical="8dp" app:layout_constrainedHeight="true" app:layout_constrainedWidth="true" app:layout_constraintBottom_toBottomOf="@id/guideline" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" app:handleThickness="3dp" app:handleColor="@*android:color/accent_device_default" app:scrimColor="#9444" tools:background="?android:colorBackground" tools:minHeight="100dp" tools:minWidth="100dp" /> <androidx.constraintlayout.widget.Guideline android:id="@+id/guideline" android:layout_width="wrap_content" Loading packages/SystemUI/res/values/attrs.xml +6 −0 Original line number Diff line number Diff line Loading @@ -171,5 +171,11 @@ <declare-styleable name="PagedTileLayout"> <attr name="sideLabels" format="boolean"/> </declare-styleable> <declare-styleable name="CropView"> <attr name="handleThickness" format="dimension" /> <attr name="handleColor" format="color" /> <attr name="scrimColor" format="color" /> </declare-styleable> </resources> packages/SystemUI/src/com/android/systemui/screenshot/CropView.java 0 → 100644 +151 −0 Original line number Diff line number Diff line /* * Copyright (C) 2021 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.screenshot; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.util.AttributeSet; import android.util.MathUtils; import android.view.MotionEvent; import android.view.View; import androidx.annotation.Nullable; import com.android.systemui.R; /** * CropView has top and bottom draggable crop handles, with a scrim to darken the areas being * cropped out. */ public class CropView extends View { private enum CropBoundary { NONE, TOP, BOTTOM } private final float mCropTouchMargin; private final Paint mShadePaint; private final Paint mHandlePaint; // Top and bottom crops are stored as floats [0, 1], representing the top and bottom of the // view, respectively. private float mTopCrop = 0f; private float mBottomCrop = 1f; private CropBoundary mCurrentDraggingBoundary = CropBoundary.NONE; private float mLastY; public CropView(Context context, @Nullable AttributeSet attrs) { this(context, attrs, 0); } public CropView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); TypedArray t = context.getTheme().obtainStyledAttributes( attrs, R.styleable.CropView, 0, 0); mShadePaint = new Paint(); mShadePaint.setColor(t.getColor(R.styleable.CropView_scrimColor, Color.TRANSPARENT)); mHandlePaint = new Paint(); mHandlePaint.setColor(t.getColor(R.styleable.CropView_handleColor, Color.BLACK)); mHandlePaint.setStrokeWidth( t.getDimensionPixelSize(R.styleable.CropView_handleThickness, 20)); t.recycle(); // 48 dp touchable region around each handle. mCropTouchMargin = 24 * getResources().getDisplayMetrics().density; } @Override public void onDraw(Canvas canvas) { super.onDraw(canvas); drawShade(canvas, 0, mTopCrop); drawShade(canvas, mBottomCrop, 1f); drawHandle(canvas, mTopCrop); drawHandle(canvas, mBottomCrop); } @Override public boolean onTouchEvent(MotionEvent event) { int topPx = fractionToPixels(mTopCrop); int bottomPx = fractionToPixels(mBottomCrop); if (event.getAction() == MotionEvent.ACTION_DOWN) { mCurrentDraggingBoundary = nearestBoundary(event, topPx, bottomPx); if (mCurrentDraggingBoundary != CropBoundary.NONE) { mLastY = event.getY(); } return true; } if (event.getAction() == MotionEvent.ACTION_MOVE && mCurrentDraggingBoundary != CropBoundary.NONE) { float delta = event.getY() - mLastY; if (mCurrentDraggingBoundary == CropBoundary.TOP) { mTopCrop = pixelsToFraction((int) MathUtils.constrain(topPx + delta, 0, bottomPx - 2 * mCropTouchMargin)); } else { // Bottom mBottomCrop = pixelsToFraction((int) MathUtils.constrain(bottomPx + delta, topPx + 2 * mCropTouchMargin, getMeasuredHeight())); } mLastY = event.getY(); invalidate(); return true; } return super.onTouchEvent(event); } /** * @return value [0,1] representing the position of the top crop boundary. */ public float getTopBoundary() { return mTopCrop; } /** * @return value [0,1] representing the position of the bottom crop boundary. */ public float getBottomBoundary() { return mBottomCrop; } private void drawShade(Canvas canvas, float fracStart, float fracEnd) { canvas.drawRect(0, fractionToPixels(fracStart), getMeasuredWidth(), fractionToPixels(fracEnd), mShadePaint); } private void drawHandle(Canvas canvas, float frac) { int y = fractionToPixels(frac); canvas.drawLine(0, y, getMeasuredWidth(), y, mHandlePaint); } private int fractionToPixels(float frac) { return (int) (frac * getMeasuredHeight()); } private float pixelsToFraction(int px) { return px / (float) getMeasuredHeight(); } private CropBoundary nearestBoundary(MotionEvent event, int topPx, int bottomPx) { if (Math.abs(event.getY() - topPx) < mCropTouchMargin) { return CropBoundary.TOP; } if (Math.abs(event.getY() - bottomPx) < mCropTouchMargin) { return CropBoundary.BOTTOM; } return CropBoundary.NONE; } } packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java +18 −11 Original line number Diff line number Diff line Loading @@ -18,10 +18,12 @@ package com.android.systemui.screenshot; import android.annotation.IdRes; import android.annotation.UiThread; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.net.Uri; import android.os.UserHandle; import android.text.TextUtils; import android.util.Log; import android.view.View; import android.view.ViewTreeObserver.InternalInsetsInfo; Loading Loading @@ -168,19 +170,20 @@ public class ScrollCaptureController implements OnComputeInternalInsetsListener } private void edit() { sendIntentWhenReady(Intent.ACTION_EDIT); String editorPackage = mContext.getString(R.string.config_screenshotEditor); sendIntentWhenReady(Intent.ACTION_EDIT, editorPackage); } private void share() { sendIntentWhenReady(Intent.ACTION_SEND); sendIntentWhenReady(Intent.ACTION_SEND, null); } void sendIntentWhenReady(String action) { void sendIntentWhenReady(String action, String component) { if (mExportFuture != null) { mExportFuture.addListener(() -> { try { ImageExporter.Result result = mExportFuture.get(); sendIntent(action, result.uri); sendIntent(action, component, result.uri); mCallback.onFinish(); } catch (InterruptedException | ExecutionException e) { Log.e(TAG, "failed to export", e); Loading Loading @@ -254,12 +257,16 @@ public class ScrollCaptureController implements OnComputeInternalInsetsListener } } void sendIntent(String action, Uri uri) { Intent editIntent = new Intent(action); editIntent.setType("image/png"); editIntent.setData(uri); editIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); editIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); mContext.startActivityAsUser(editIntent, UserHandle.CURRENT); void sendIntent(String action, String component, Uri uri) { Intent intent = new Intent(action); if (!TextUtils.isEmpty(component)) { intent.setComponent(ComponentName.unflattenFromString(component)); } intent.setType("image/png"); intent.setData(uri); intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); mContext.startActivityAsUser(intent, UserHandle.CURRENT); } } Loading
packages/SystemUI/res/layout/long_screenshot.xml +19 −0 Original line number Diff line number Diff line Loading @@ -39,6 +39,25 @@ tools:minHeight="100dp" tools:minWidth="100dp" /> <com.android.systemui.screenshot.CropView android:id="@+id/crop_view" android:visibility="gone" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginVertical="8dp" app:layout_constrainedHeight="true" app:layout_constrainedWidth="true" app:layout_constraintBottom_toBottomOf="@id/guideline" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" app:handleThickness="3dp" app:handleColor="@*android:color/accent_device_default" app:scrimColor="#9444" tools:background="?android:colorBackground" tools:minHeight="100dp" tools:minWidth="100dp" /> <androidx.constraintlayout.widget.Guideline android:id="@+id/guideline" android:layout_width="wrap_content" Loading
packages/SystemUI/res/values/attrs.xml +6 −0 Original line number Diff line number Diff line Loading @@ -171,5 +171,11 @@ <declare-styleable name="PagedTileLayout"> <attr name="sideLabels" format="boolean"/> </declare-styleable> <declare-styleable name="CropView"> <attr name="handleThickness" format="dimension" /> <attr name="handleColor" format="color" /> <attr name="scrimColor" format="color" /> </declare-styleable> </resources>
packages/SystemUI/src/com/android/systemui/screenshot/CropView.java 0 → 100644 +151 −0 Original line number Diff line number Diff line /* * Copyright (C) 2021 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.screenshot; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.util.AttributeSet; import android.util.MathUtils; import android.view.MotionEvent; import android.view.View; import androidx.annotation.Nullable; import com.android.systemui.R; /** * CropView has top and bottom draggable crop handles, with a scrim to darken the areas being * cropped out. */ public class CropView extends View { private enum CropBoundary { NONE, TOP, BOTTOM } private final float mCropTouchMargin; private final Paint mShadePaint; private final Paint mHandlePaint; // Top and bottom crops are stored as floats [0, 1], representing the top and bottom of the // view, respectively. private float mTopCrop = 0f; private float mBottomCrop = 1f; private CropBoundary mCurrentDraggingBoundary = CropBoundary.NONE; private float mLastY; public CropView(Context context, @Nullable AttributeSet attrs) { this(context, attrs, 0); } public CropView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); TypedArray t = context.getTheme().obtainStyledAttributes( attrs, R.styleable.CropView, 0, 0); mShadePaint = new Paint(); mShadePaint.setColor(t.getColor(R.styleable.CropView_scrimColor, Color.TRANSPARENT)); mHandlePaint = new Paint(); mHandlePaint.setColor(t.getColor(R.styleable.CropView_handleColor, Color.BLACK)); mHandlePaint.setStrokeWidth( t.getDimensionPixelSize(R.styleable.CropView_handleThickness, 20)); t.recycle(); // 48 dp touchable region around each handle. mCropTouchMargin = 24 * getResources().getDisplayMetrics().density; } @Override public void onDraw(Canvas canvas) { super.onDraw(canvas); drawShade(canvas, 0, mTopCrop); drawShade(canvas, mBottomCrop, 1f); drawHandle(canvas, mTopCrop); drawHandle(canvas, mBottomCrop); } @Override public boolean onTouchEvent(MotionEvent event) { int topPx = fractionToPixels(mTopCrop); int bottomPx = fractionToPixels(mBottomCrop); if (event.getAction() == MotionEvent.ACTION_DOWN) { mCurrentDraggingBoundary = nearestBoundary(event, topPx, bottomPx); if (mCurrentDraggingBoundary != CropBoundary.NONE) { mLastY = event.getY(); } return true; } if (event.getAction() == MotionEvent.ACTION_MOVE && mCurrentDraggingBoundary != CropBoundary.NONE) { float delta = event.getY() - mLastY; if (mCurrentDraggingBoundary == CropBoundary.TOP) { mTopCrop = pixelsToFraction((int) MathUtils.constrain(topPx + delta, 0, bottomPx - 2 * mCropTouchMargin)); } else { // Bottom mBottomCrop = pixelsToFraction((int) MathUtils.constrain(bottomPx + delta, topPx + 2 * mCropTouchMargin, getMeasuredHeight())); } mLastY = event.getY(); invalidate(); return true; } return super.onTouchEvent(event); } /** * @return value [0,1] representing the position of the top crop boundary. */ public float getTopBoundary() { return mTopCrop; } /** * @return value [0,1] representing the position of the bottom crop boundary. */ public float getBottomBoundary() { return mBottomCrop; } private void drawShade(Canvas canvas, float fracStart, float fracEnd) { canvas.drawRect(0, fractionToPixels(fracStart), getMeasuredWidth(), fractionToPixels(fracEnd), mShadePaint); } private void drawHandle(Canvas canvas, float frac) { int y = fractionToPixels(frac); canvas.drawLine(0, y, getMeasuredWidth(), y, mHandlePaint); } private int fractionToPixels(float frac) { return (int) (frac * getMeasuredHeight()); } private float pixelsToFraction(int px) { return px / (float) getMeasuredHeight(); } private CropBoundary nearestBoundary(MotionEvent event, int topPx, int bottomPx) { if (Math.abs(event.getY() - topPx) < mCropTouchMargin) { return CropBoundary.TOP; } if (Math.abs(event.getY() - bottomPx) < mCropTouchMargin) { return CropBoundary.BOTTOM; } return CropBoundary.NONE; } }
packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java +18 −11 Original line number Diff line number Diff line Loading @@ -18,10 +18,12 @@ package com.android.systemui.screenshot; import android.annotation.IdRes; import android.annotation.UiThread; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.net.Uri; import android.os.UserHandle; import android.text.TextUtils; import android.util.Log; import android.view.View; import android.view.ViewTreeObserver.InternalInsetsInfo; Loading Loading @@ -168,19 +170,20 @@ public class ScrollCaptureController implements OnComputeInternalInsetsListener } private void edit() { sendIntentWhenReady(Intent.ACTION_EDIT); String editorPackage = mContext.getString(R.string.config_screenshotEditor); sendIntentWhenReady(Intent.ACTION_EDIT, editorPackage); } private void share() { sendIntentWhenReady(Intent.ACTION_SEND); sendIntentWhenReady(Intent.ACTION_SEND, null); } void sendIntentWhenReady(String action) { void sendIntentWhenReady(String action, String component) { if (mExportFuture != null) { mExportFuture.addListener(() -> { try { ImageExporter.Result result = mExportFuture.get(); sendIntent(action, result.uri); sendIntent(action, component, result.uri); mCallback.onFinish(); } catch (InterruptedException | ExecutionException e) { Log.e(TAG, "failed to export", e); Loading Loading @@ -254,12 +257,16 @@ public class ScrollCaptureController implements OnComputeInternalInsetsListener } } void sendIntent(String action, Uri uri) { Intent editIntent = new Intent(action); editIntent.setType("image/png"); editIntent.setData(uri); editIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); editIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); mContext.startActivityAsUser(editIntent, UserHandle.CURRENT); void sendIntent(String action, String component, Uri uri) { Intent intent = new Intent(action); if (!TextUtils.isEmpty(component)) { intent.setComponent(ComponentName.unflattenFromString(component)); } intent.setType("image/png"); intent.setData(uri); intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); mContext.startActivityAsUser(intent, UserHandle.CURRENT); } }