diff options
Diffstat (limited to 'libs')
9 files changed, 117 insertions, 36 deletions
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java index c2ad1a98d167..5b7d141591ea 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java @@ -69,6 +69,7 @@ import com.android.wm.shell.common.InteractionJankMonitorUtils;  import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition;  import java.io.PrintWriter; +import java.util.function.Consumer;  /**   * Records and handles layout of splits. Helps to calculate proper bounds when configuration or @@ -599,7 +600,7 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange      /** Swich both surface position with animation. */      public void splitSwitching(SurfaceControl.Transaction t, SurfaceControl leash1, -            SurfaceControl leash2, Runnable finishCallback) { +            SurfaceControl leash2, Consumer<Rect> finishCallback) {          final boolean isLandscape = isLandscape();          final Rect insets = getDisplayInsets(mContext);          insets.set(isLandscape ? insets.left : 0, isLandscape ? 0 : insets.top, @@ -617,18 +618,13 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange          distBounds1.offset(-mRootBounds.left, -mRootBounds.top);          distBounds2.offset(-mRootBounds.left, -mRootBounds.top);          distDividerBounds.offset(-mRootBounds.left, -mRootBounds.top); -        // DO NOT move to insets area for smooth animation. -        distBounds1.set(distBounds1.left, distBounds1.top, -                distBounds1.right - insets.right, distBounds1.bottom - insets.bottom); -        distBounds2.set(distBounds2.left + insets.left, distBounds2.top + insets.top, -                distBounds2.right, distBounds2.bottom);          ValueAnimator animator1 = moveSurface(t, leash1, getRefBounds1(), distBounds1, -                false /* alignStart */); +                -insets.left, -insets.top);          ValueAnimator animator2 = moveSurface(t, leash2, getRefBounds2(), distBounds2, -                true /* alignStart */); +                insets.left, insets.top);          ValueAnimator animator3 = moveSurface(t, getDividerLeash(), getRefDividerBounds(), -                distDividerBounds, true /* alignStart */); +                distDividerBounds, 0 /* offsetX */, 0 /* offsetY */);          AnimatorSet set = new AnimatorSet();          set.playTogether(animator1, animator2, animator3); @@ -638,14 +634,14 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange              public void onAnimationEnd(Animator animation) {                  mDividePosition = dividerPos;                  updateBounds(mDividePosition); -                finishCallback.run(); +                finishCallback.accept(insets);              }          });          set.start();      }      private ValueAnimator moveSurface(SurfaceControl.Transaction t, SurfaceControl leash, -            Rect start, Rect end, boolean alignStart) { +            Rect start, Rect end, float offsetX, float offsetY) {          Rect tempStart = new Rect(start);          Rect tempEnd = new Rect(end);          final float diffX = tempEnd.left - tempStart.left; @@ -661,15 +657,15 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange              final float distY = tempStart.top + scale * diffY;              final int width = (int) (tempStart.width() + scale * diffWidth);              final int height = (int) (tempStart.height() + scale * diffHeight); -            if (alignStart) { +            if (offsetX == 0 && offsetY == 0) {                  t.setPosition(leash, distX, distY);                  t.setWindowCrop(leash, width, height);              } else { -                final int offsetX = width - tempStart.width(); -                final int offsetY = height - tempStart.height(); -                t.setPosition(leash, distX + offsetX, distY + offsetY); +                final int diffOffsetX = (int) (scale * offsetX); +                final int diffOffsetY = (int) (scale * offsetY); +                t.setPosition(leash, distX + diffOffsetX, distY + diffOffsetY);                  mTempRect.set(0, 0, width, height); -                mTempRect.offsetTo(-offsetX, -offsetY); +                mTempRect.offsetTo(-diffOffsetX, -diffOffsetY);                  t.setCrop(leash, mTempRect);              }              t.apply(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java index 2b36b4c0307d..85bad174194c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java @@ -335,6 +335,7 @@ public class PipTransition extends PipTransitionController {          final ActivityManager.RunningTaskInfo taskInfo = mPipOrganizer.getTaskInfo();          if (taskInfo != null) {              startExpandAnimation(taskInfo, mPipOrganizer.getSurfaceControl(), +                    mPipBoundsState.getBounds(), mPipBoundsState.getBounds(),                      new Rect(mExitDestinationBounds), Surface.ROTATION_0);          }          mExitDestinationBounds.setEmpty(); @@ -475,6 +476,20 @@ public class PipTransition extends PipTransitionController {                      taskInfo);              return;          } + +        // When exiting PiP, the PiP leash may be an Activity of a multi-windowing Task, for which +        // case it may not be in the screen coordinate. +        // Reparent the pip leash to the root with max layer so that we can animate it outside of +        // parent crop, and make sure it is not covered by other windows. +        final SurfaceControl pipLeash = pipChange.getLeash(); +        startTransaction.reparent(pipLeash, info.getRootLeash()); +        startTransaction.setLayer(pipLeash, Integer.MAX_VALUE); +        // Note: because of this, the bounds to animate should be translated to the root coordinate. +        final Point offset = info.getRootOffset(); +        final Rect currentBounds = mPipBoundsState.getBounds(); +        currentBounds.offset(-offset.x, -offset.y); +        startTransaction.setPosition(pipLeash, currentBounds.left, currentBounds.top); +          mFinishCallback = (wct, wctCB) -> {              mPipOrganizer.onExitPipFinished(taskInfo);              finishCallback.onTransitionFinished(wct, wctCB); @@ -496,18 +511,17 @@ public class PipTransition extends PipTransitionController {              if (displayRotationChange != null) {                  // Exiting PIP to fullscreen with orientation change.                  startExpandAndRotationAnimation(info, startTransaction, finishTransaction, -                        displayRotationChange, taskInfo, pipChange); +                        displayRotationChange, taskInfo, pipChange, offset);                  return;              }          }          // Set the initial frame as scaling the end to the start.          final Rect destinationBounds = new Rect(pipChange.getEndAbsBounds()); -        final Point offset = pipChange.getEndRelOffset();          destinationBounds.offset(-offset.x, -offset.y); -        startTransaction.setWindowCrop(pipChange.getLeash(), destinationBounds); -        mSurfaceTransactionHelper.scale(startTransaction, pipChange.getLeash(), -                destinationBounds, mPipBoundsState.getBounds()); +        startTransaction.setWindowCrop(pipLeash, destinationBounds); +        mSurfaceTransactionHelper.scale(startTransaction, pipLeash, destinationBounds, +                currentBounds);          startTransaction.apply();          // Check if it is fixed rotation. @@ -532,19 +546,21 @@ public class PipTransition extends PipTransitionController {                  y = destinationBounds.bottom;              }              mSurfaceTransactionHelper.rotateAndScaleWithCrop(finishTransaction, -                    pipChange.getLeash(), endBounds, endBounds, new Rect(), degree, x, y, +                    pipLeash, endBounds, endBounds, new Rect(), degree, x, y,                      true /* isExpanding */, rotationDelta == ROTATION_270 /* clockwise */);          } else {              rotationDelta = Surface.ROTATION_0;          } -        startExpandAnimation(taskInfo, pipChange.getLeash(), destinationBounds, rotationDelta); +        startExpandAnimation(taskInfo, pipLeash, currentBounds, currentBounds, destinationBounds, +                rotationDelta);      }      private void startExpandAndRotationAnimation(@NonNull TransitionInfo info,              @NonNull SurfaceControl.Transaction startTransaction,              @NonNull SurfaceControl.Transaction finishTransaction,              @NonNull TransitionInfo.Change displayRotationChange, -            @NonNull TaskInfo taskInfo, @NonNull TransitionInfo.Change pipChange) { +            @NonNull TaskInfo taskInfo, @NonNull TransitionInfo.Change pipChange, +            @NonNull Point offset) {          final int rotateDelta = deltaRotation(displayRotationChange.getStartRotation(),                  displayRotationChange.getEndRotation()); @@ -556,7 +572,6 @@ public class PipTransition extends PipTransitionController {          final Rect startBounds = new Rect(pipChange.getStartAbsBounds());          rotateBounds(startBounds, displayRotationChange.getStartAbsBounds(), rotateDelta);          final Rect endBounds = new Rect(pipChange.getEndAbsBounds()); -        final Point offset = pipChange.getEndRelOffset();          startBounds.offset(-offset.x, -offset.y);          endBounds.offset(-offset.x, -offset.y); @@ -592,11 +607,12 @@ public class PipTransition extends PipTransitionController {      }      private void startExpandAnimation(final TaskInfo taskInfo, final SurfaceControl leash, -            final Rect destinationBounds, final int rotationDelta) { +            final Rect baseBounds, final Rect startBounds, final Rect endBounds, +            final int rotationDelta) {          final PipAnimationController.PipTransitionAnimator animator = -                mPipAnimationController.getAnimator(taskInfo, leash, mPipBoundsState.getBounds(), -                        mPipBoundsState.getBounds(), destinationBounds, null, -                        TRANSITION_DIRECTION_LEAVE_PIP, 0 /* startingAngle */, rotationDelta); +                mPipAnimationController.getAnimator(taskInfo, leash, baseBounds, startBounds, +                        endBounds, null /* sourceHintRect */, TRANSITION_DIRECTION_LEAVE_PIP, +                        0 /* startingAngle */, rotationDelta);          animator.setTransitionDirection(TRANSITION_DIRECTION_LEAVE_PIP)                  .setPipAnimationCallback(mPipAnimationCallback)                  .setDuration(mEnterExitAnimationDuration) diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java index 943419bb8ea2..15a11334307b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java @@ -115,6 +115,7 @@ import com.android.wm.shell.common.DisplayController;  import com.android.wm.shell.common.DisplayImeController;  import com.android.wm.shell.common.DisplayInsetsController;  import com.android.wm.shell.common.DisplayLayout; +import com.android.wm.shell.common.ScreenshotUtils;  import com.android.wm.shell.common.ShellExecutor;  import com.android.wm.shell.common.SyncTransactionQueue;  import com.android.wm.shell.common.TransactionPool; @@ -846,15 +847,44 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,      void setSideStagePositionAnimated(@SplitPosition int sideStagePosition) {          if (mSideStagePosition == sideStagePosition) return;          SurfaceControl.Transaction t = mTransactionPool.acquire(); +        mTempRect1.setEmpty();          final StageTaskListener topLeftStage =                  mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mSideStage : mMainStage; +        final SurfaceControl topLeftScreenshot = ScreenshotUtils.takeScreenshot(t, +                topLeftStage.mRootLeash, mTempRect1, Integer.MAX_VALUE - 1);          final StageTaskListener bottomRightStage =                  mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mMainStage : mSideStage; +        final SurfaceControl bottomRightScreenshot = ScreenshotUtils.takeScreenshot(t, +                bottomRightStage.mRootLeash, mTempRect1, Integer.MAX_VALUE - 1);          mSplitLayout.splitSwitching(t, topLeftStage.mRootLeash, bottomRightStage.mRootLeash, -                () -> { -                    setSideStagePosition(SplitLayout.reversePosition(mSideStagePosition), -                            null /* wct */); -                    mTransactionPool.release(t); +                insets -> { +                    WindowContainerTransaction wct = new WindowContainerTransaction(); +                    setSideStagePosition(SplitLayout.reversePosition(mSideStagePosition), wct); +                    mSyncQueue.queue(wct); +                    mSyncQueue.runInSync(st -> { +                        updateSurfaceBounds(mSplitLayout, st, false /* applyResizingOffset */); +                        st.setPosition(topLeftScreenshot, -insets.left, -insets.top); +                        st.setPosition(bottomRightScreenshot, insets.left, insets.top); + +                        final ValueAnimator va = ValueAnimator.ofFloat(1, 0); +                        va.addUpdateListener(valueAnimator-> { +                            final float progress = (float) valueAnimator.getAnimatedValue(); +                            t.setAlpha(topLeftScreenshot, progress); +                            t.setAlpha(bottomRightScreenshot, progress); +                            t.apply(); +                        }); +                        va.addListener(new AnimatorListenerAdapter() { +                            @Override +                            public void onAnimationEnd( +                                    @androidx.annotation.NonNull Animator animation) { +                                t.remove(topLeftScreenshot); +                                t.remove(bottomRightScreenshot); +                                t.apply(); +                                mTransactionPool.release(t); +                            } +                        }); +                        va.start(); +                    });                  });      } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java index af79386caf9c..928e71f8d3a6 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java @@ -46,6 +46,7 @@ import static android.window.TransitionInfo.FLAG_CROSS_PROFILE_WORK_THUMBNAIL;  import static android.window.TransitionInfo.FLAG_DISPLAY_HAS_ALERT_WINDOWS;  import static android.window.TransitionInfo.FLAG_FILLS_TASK;  import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY; +import static android.window.TransitionInfo.FLAG_IS_BEHIND_STARTING_WINDOW;  import static android.window.TransitionInfo.FLAG_IS_DISPLAY;  import static android.window.TransitionInfo.FLAG_IS_VOICE_INTERACTION;  import static android.window.TransitionInfo.FLAG_IS_WALLPAPER; @@ -319,6 +320,17 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {          final int wallpaperTransit = getWallpaperTransitType(info);          for (int i = info.getChanges().size() - 1; i >= 0; --i) {              final TransitionInfo.Change change = info.getChanges().get(i); +            if (change.hasAllFlags(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY +                    | FLAG_IS_BEHIND_STARTING_WINDOW)) { +                // Don't animate embedded activity if it is covered by the starting window. +                // Non-embedded case still needs animation because the container can still animate +                // the starting window together, e.g. CLOSE or CHANGE type. +                continue; +            } +            if (change.hasFlags(TransitionInfo.FLAGS_IS_NON_APP_WINDOW)) { +                // Wallpaper, IME, and system windows don't need any default animations. +                continue; +            }              final boolean isTask = change.getTaskInfo() != null;              boolean isSeamlessDisplayChange = false; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java index 63d31cde4715..89205a6dfb01 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java @@ -341,6 +341,15 @@ public class Transitions implements RemoteCallable<Transitions> {              final SurfaceControl leash = change.getLeash();              final int mode = info.getChanges().get(i).getMode(); +            if (mode == TRANSIT_TO_FRONT +                    && ((change.getStartAbsBounds().height() != change.getEndAbsBounds().height() +                    || change.getStartAbsBounds().width() != change.getEndAbsBounds().width()))) { +                // When the window is moved to front with a different size, make sure the crop is +                // updated to prevent it from using the old crop. +                t.setWindowCrop(leash, change.getEndAbsBounds().width(), +                        change.getEndAbsBounds().height()); +            } +              // Don't move anything that isn't independent within its parents              if (!TransitionInfo.isIndependent(change, info)) {                  if (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT || mode == TRANSIT_CHANGE) { diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt index 73159c981b82..ad7a531b589d 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt @@ -145,15 +145,19 @@ class SwitchAppByDoubleTapDivider(testSpec: FlickerTestParameter) : SplitScreenB          // robust enough to get the correct end state.      } +    @Presubmit      @Test      fun splitScreenDividerKeepVisible() = testSpec.layerKeepVisible(SPLIT_SCREEN_DIVIDER_COMPONENT) +    @Presubmit      @Test      fun primaryAppLayerIsVisibleAtEnd() = testSpec.layerIsVisibleAtEnd(primaryApp) +    @Presubmit      @Test      fun secondaryAppLayerIsVisibleAtEnd() = testSpec.layerIsVisibleAtEnd(secondaryApp) +    @Presubmit      @Test      fun primaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(          primaryApp, @@ -161,6 +165,7 @@ class SwitchAppByDoubleTapDivider(testSpec: FlickerTestParameter) : SplitScreenB          portraitPosTop = true      ) +    @Presubmit      @Test      fun secondaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(          secondaryApp, @@ -168,9 +173,11 @@ class SwitchAppByDoubleTapDivider(testSpec: FlickerTestParameter) : SplitScreenB          portraitPosTop = false      ) +    @Presubmit      @Test      fun primaryAppWindowIsVisibleAtEnd() = testSpec.appWindowIsVisibleAtEnd(primaryApp) +    @Presubmit      @Test      fun secondaryAppWindowIsVisibleAtEnd() = testSpec.appWindowIsVisibleAtEnd(secondaryApp) diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java index 55883ab2ef70..d01f3d310fc3 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java @@ -39,6 +39,7 @@ import android.content.ComponentName;  import android.content.Intent;  import android.content.pm.ActivityInfo; +import androidx.test.annotation.UiThreadTest;  import androidx.test.ext.junit.runners.AndroidJUnit4;  import androidx.test.filters.SmallTest; @@ -110,6 +111,7 @@ public class SplitScreenControllerTests extends ShellTestCase {      }      @Test +    @UiThreadTest      public void instantiateController_registerDumpCallback() {          doReturn(mMainExecutor).when(mTaskOrganizer).getExecutor();          when(mDisplayController.getDisplayLayout(anyInt())).thenReturn(new DisplayLayout()); @@ -118,6 +120,7 @@ public class SplitScreenControllerTests extends ShellTestCase {      }      @Test +    @UiThreadTest      public void instantiateController_registerCommandCallback() {          doReturn(mMainExecutor).when(mTaskOrganizer).getExecutor();          when(mDisplayController.getDisplayLayout(anyInt())).thenReturn(new DisplayLayout()); @@ -126,6 +129,7 @@ public class SplitScreenControllerTests extends ShellTestCase {      }      @Test +    @UiThreadTest      public void testControllerRegistersKeyguardChangeListener() {          doReturn(mMainExecutor).when(mTaskOrganizer).getExecutor();          when(mDisplayController.getDisplayLayout(anyInt())).thenReturn(new DisplayLayout()); @@ -134,6 +138,7 @@ public class SplitScreenControllerTests extends ShellTestCase {      }      @Test +    @UiThreadTest      public void instantiateController_addExternalInterface() {          doReturn(mMainExecutor).when(mTaskOrganizer).getExecutor();          when(mDisplayController.getDisplayLayout(anyInt())).thenReturn(new DisplayLayout()); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java index 835087007b30..3569860b6128 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java @@ -50,6 +50,7 @@ import android.view.SurfaceSession;  import android.window.WindowContainerToken;  import android.window.WindowContainerTransaction; +import androidx.test.annotation.UiThreadTest;  import androidx.test.ext.junit.runners.AndroidJUnit4;  import androidx.test.filters.SmallTest; @@ -113,6 +114,7 @@ public class StageCoordinatorTests extends ShellTestCase {      private StageCoordinator mStageCoordinator;      @Before +    @UiThreadTest      public void setup() {          MockitoAnnotations.initMocks(this);          mStageCoordinator = spy(new StageCoordinator(mContext, DEFAULT_DISPLAY, mSyncQueue, diff --git a/libs/hwui/jni/android_graphics_Canvas.cpp b/libs/hwui/jni/android_graphics_Canvas.cpp index 0513447ed05e..35258a3e0774 100644 --- a/libs/hwui/jni/android_graphics_Canvas.cpp +++ b/libs/hwui/jni/android_graphics_Canvas.cpp @@ -20,6 +20,7 @@  #include <android/api-level.h>  #else  #define __ANDROID_API_P__ 28 +#define __ANDROID_API_U__ 34  #endif  #include <androidfw/ResourceTypes.h>  #include <hwui/Canvas.h> @@ -30,8 +31,9 @@  #include <nativehelper/ScopedPrimitiveArray.h>  #include <nativehelper/ScopedStringChars.h> -#include "FontUtils.h"  #include "Bitmap.h" +#include "FontUtils.h" +#include "SkAndroidFrameworkUtils.h"  #include "SkBitmap.h"  #include "SkBlendMode.h"  #include "SkClipOp.h" @@ -42,10 +44,10 @@  #include "SkMatrix.h"  #include "SkPath.h"  #include "SkPoint.h" +#include "SkRRect.h"  #include "SkRect.h"  #include "SkRefCnt.h"  #include "SkRegion.h" -#include "SkRRect.h"  #include "SkScalar.h"  #include "SkVertices.h" @@ -710,6 +712,9 @@ static void freeTextLayoutCaches(JNIEnv* env, jobject) {  static void setCompatibilityVersion(JNIEnv* env, jobject, jint apiLevel) {      Canvas::setCompatibilityVersion(apiLevel); +    if (apiLevel < __ANDROID_API_U__) { +        SkAndroidFrameworkUtils::UseLegacyLocalMatrixConcatenation(); +    }  }  static void punchHole(JNIEnv* env, jobject, jlong canvasPtr, jfloat left, jfloat top, jfloat right, @@ -800,7 +805,6 @@ int register_android_graphics_Canvas(JNIEnv* env) {      ret |= RegisterMethodsOrDie(env, "android/graphics/BaseCanvas", gDrawMethods, NELEM(gDrawMethods));      ret |= RegisterMethodsOrDie(env, "android/graphics/BaseRecordingCanvas", gDrawMethods, NELEM(gDrawMethods));      return ret; -  }  }; // namespace android  |