diff --git a/packages/SystemUI/res/layout/long_screenshot.xml b/packages/SystemUI/res/layout/long_screenshot.xml index fc68a64ac9bd0..2a055fc8252f2 100644 --- a/packages/SystemUI/res/layout/long_screenshot.xml +++ b/packages/SystemUI/res/layout/long_screenshot.xml @@ -39,6 +39,25 @@ tools:minHeight="100dp" tools:minWidth="100dp" /> + + + + + + + + diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/CropView.java b/packages/SystemUI/src/com/android/systemui/screenshot/CropView.java new file mode 100644 index 0000000000000..8e182b415488a --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/screenshot/CropView.java @@ -0,0 +1,151 @@ +/* + * 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; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java index 176a2c78796b8..fd7db4bfffb42 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java @@ -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; @@ -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); @@ -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); } }