diff options
Diffstat (limited to 'libs/WindowManager/Shell')
16 files changed, 363 insertions, 147 deletions
diff --git a/libs/WindowManager/Shell/res/color/one_handed_tutorial_background_color.xml b/libs/WindowManager/Shell/res/color/one_handed_tutorial_background_color.xml new file mode 100644 index 000000000000..4f56e0f023a4 --- /dev/null +++ b/libs/WindowManager/Shell/res/color/one_handed_tutorial_background_color.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ 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. + --> + +<selector xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"> + <item android:color="?androidprv:attr/colorSurfaceVariant"/> +</selector> diff --git a/libs/WindowManager/Shell/res/drawable-hdpi/one_handed_tutorial.png b/libs/WindowManager/Shell/res/drawable-hdpi/one_handed_tutorial.png Binary files differdeleted file mode 100644 index 6c1f1cfdea7c..000000000000 --- a/libs/WindowManager/Shell/res/drawable-hdpi/one_handed_tutorial.png +++ /dev/null diff --git a/libs/WindowManager/Shell/res/drawable/one_handed_tutorial_icon.xml b/libs/WindowManager/Shell/res/drawable/one_handed_tutorial_icon.xml new file mode 100644 index 000000000000..b32f34ef7c58 --- /dev/null +++ b/libs/WindowManager/Shell/res/drawable/one_handed_tutorial_icon.xml @@ -0,0 +1,14 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="32dp" + android:height="60dp" + android:viewportWidth="32" + android:viewportHeight="60"> + <path + android:pathData="M1.9703,30.5041C1.9703,28.295 3.7612,26.5042 5.9703,26.5042H25.5551C27.7642,26.5042 29.5551,28.295 29.5551,30.5042V54.0296C29.5551,56.2387 27.7642,58.0296 25.5551,58.0296H5.9703C3.7612,58.0296 1.9703,56.2387 1.9703,54.0296V30.5041Z" + android:fillColor="#000000" + android:fillAlpha="0.16"/> + <path + android:pathData="M25.5254,2H6C3.7909,2 2,3.7909 2,6V54C2,56.2091 3.7909,58 6,58H25.5254C27.7346,58 29.5254,56.2091 29.5254,54V6C29.5254,3.7909 27.7346,2 25.5254,2ZM6,0C2.6863,0 0,2.6863 0,6V54C0,57.3137 2.6863,60 6,60H25.5254C28.8391,60 31.5254,57.3137 31.5254,54V6C31.5254,2.6863 28.8391,0 25.5254,0H6ZM12.2034,47.2336L12.8307,47.861L15.3178,45.3783V52.1277H16.2076V45.3783L18.6903,47.8654L19.322,47.2336L15.7627,43.6743L12.2034,47.2336ZM19.7034,55.0742H11.822V56.552H19.7034V55.0742Z" + android:fillColor="#000000" + android:fillType="evenOdd"/> +</vector> diff --git a/libs/WindowManager/Shell/res/layout/one_handed_tutorial.xml b/libs/WindowManager/Shell/res/layout/one_handed_tutorial.xml index 0190aad2d0ef..d29ed8b5a9ec 100644 --- a/libs/WindowManager/Shell/res/layout/one_handed_tutorial.xml +++ b/libs/WindowManager/Shell/res/layout/one_handed_tutorial.xml @@ -31,7 +31,7 @@ android:layout_marginTop="6dp" android:layout_marginBottom="0dp" android:gravity="center_horizontal" - android:src="@drawable/one_handed_tutorial" + android:src="@drawable/one_handed_tutorial_icon" android:scaleType="centerInside" /> <TextView @@ -45,7 +45,6 @@ android:fontFamily="google-sans-medium" android:text="@string/one_handed_tutorial_title" android:textSize="16sp" - android:textStyle="bold" android:textColor="@android:color/white"/> <TextView @@ -54,8 +53,8 @@ android:layout_height="wrap_content" android:layout_marginTop="6dp" android:layout_marginBottom="0dp" - android:layout_marginStart="46dp" - android:layout_marginEnd="46dp" + android:layout_marginStart="60dp" + android:layout_marginEnd="60dp" android:gravity="center_horizontal" android:fontFamily="roboto-regular" android:text="@string/one_handed_tutorial_description" diff --git a/libs/WindowManager/Shell/res/values/config.xml b/libs/WindowManager/Shell/res/values/config.xml index 8cea869aea34..d0e4f7a02ffc 100644 --- a/libs/WindowManager/Shell/res/values/config.xml +++ b/libs/WindowManager/Shell/res/values/config.xml @@ -36,6 +36,9 @@ <!-- Allow PIP to resize via dragging the corner of PiP. --> <bool name="config_pipEnableDragCornerResize">false</bool> + <!-- PiP minimum size, which is a % based off the shorter side of display width and height --> + <fraction name="config_pipShortestEdgePercent">40%</fraction> + <!-- Animation duration when using long press on recents to dock --> <integer name="long_press_dock_anim_duration">250</integer> diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java index 92e455ce4e3a..c0df06f2954f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java @@ -45,6 +45,7 @@ import android.view.Choreographer; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.SurfaceControl; +import android.view.SurfaceHolder; import android.view.SurfaceView; import android.view.View; import android.view.ViewGroup; @@ -205,6 +206,7 @@ public class BubbleStackView extends FrameLayout * between bubble activities without needing both to be alive at the same time. */ private SurfaceView mAnimatingOutSurfaceView; + private boolean mAnimatingOutSurfaceReady; /** Container for the animating-out SurfaceView. */ private FrameLayout mAnimatingOutSurfaceContainer; @@ -811,6 +813,20 @@ public class BubbleStackView extends FrameLayout mAnimatingOutSurfaceView.setZOrderOnTop(true); mAnimatingOutSurfaceView.setCornerRadius(mCornerRadius); mAnimatingOutSurfaceView.setLayoutParams(new ViewGroup.LayoutParams(0, 0)); + mAnimatingOutSurfaceView.getHolder().addCallback(new SurfaceHolder.Callback() { + @Override + public void surfaceChanged(SurfaceHolder surfaceHolder, int i, int i1, int i2) {} + + @Override + public void surfaceCreated(SurfaceHolder surfaceHolder) { + mAnimatingOutSurfaceReady = true; + } + + @Override + public void surfaceDestroyed(SurfaceHolder surfaceHolder) { + mAnimatingOutSurfaceReady = false; + } + }); mAnimatingOutSurfaceContainer.addView(mAnimatingOutSurfaceView); mAnimatingOutSurfaceContainer.setPadding( @@ -2653,7 +2669,7 @@ public class BubbleStackView extends FrameLayout return; } - if (!mIsExpanded) { + if (!mIsExpanded || !mAnimatingOutSurfaceReady) { onComplete.accept(false); return; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedAnimationController.java index 1ae263624186..bfb2cc6a8fc5 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedAnimationController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedAnimationController.java @@ -163,6 +163,7 @@ public class OneHandedAnimationController { mOneHandedAnimationCallbacks.forEach( (callback) -> callback.onOneHandedAnimationEnd(tx, this) ); + mOneHandedAnimationCallbacks.clear(); } @Override @@ -171,6 +172,7 @@ public class OneHandedAnimationController { mOneHandedAnimationCallbacks.forEach( (callback) -> callback.onOneHandedAnimationCancel(this) ); + mOneHandedAnimationCallbacks.clear(); } @Override @@ -182,7 +184,7 @@ public class OneHandedAnimationController { final SurfaceControl.Transaction tx = newSurfaceControlTransaction(); applySurfaceControlTransaction(mLeash, tx, animation.getAnimatedFraction()); mOneHandedAnimationCallbacks.forEach( - (callback) -> callback.onAnimationUpdate(0f, (float) mCurrentValue) + (callback) -> callback.onAnimationUpdate(0f, mCurrentValue) ); } @@ -216,7 +218,7 @@ public class OneHandedAnimationController { } float getDestinationOffset() { - return ((float) mEndValue - (float) mStartValue); + return (mEndValue - mStartValue); } @TransitionDirection @@ -302,7 +304,7 @@ public class OneHandedAnimationController { @Override public float getInterpolation(float input) { return (float) (Math.pow(2, -10 * input) * Math.sin(((input - 4.0f) / 4.0f) - * (2.0f * Math.PI) / 4.0f) + 1); + * (2.0f * Math.PI) / 4.0f) + 1.0f); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedBackgroundPanelOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedBackgroundPanelOrganizer.java index 481b94817385..3ccb9e7570d5 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedBackgroundPanelOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedBackgroundPanelOrganizer.java @@ -20,7 +20,6 @@ import android.content.Context; import android.graphics.Color; import android.graphics.PixelFormat; import android.graphics.Rect; -import android.util.Log; import android.view.SurfaceControl; import android.view.SurfaceSession; import android.window.DisplayAreaAppearedInfo; @@ -30,7 +29,6 @@ import android.window.DisplayAreaOrganizer; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; -import androidx.core.content.ContextCompat; import com.android.internal.annotations.GuardedBy; import com.android.wm.shell.R; @@ -48,14 +46,17 @@ import java.util.concurrent.Executor; public class OneHandedBackgroundPanelOrganizer extends DisplayAreaOrganizer implements OneHandedTransitionCallback { private static final String TAG = "OneHandedBackgroundPanelOrganizer"; + private static final int THEME_COLOR_OFFSET = 10; + private final Context mContext; private final Object mLock = new Object(); private final SurfaceSession mSurfaceSession = new SurfaceSession(); - private final float[] mDefaultColor; private final Executor mMainExecutor; private final OneHandedSurfaceTransactionHelper.SurfaceControlTransactionFactory mSurfaceControlTransactionFactory; + private float[] mDefaultColor; + /** * The background to distinguish the boundary of translated windows and empty region when * one handed mode triggered. @@ -88,15 +89,14 @@ public class OneHandedBackgroundPanelOrganizer extends DisplayAreaOrganizer public OneHandedBackgroundPanelOrganizer(Context context, DisplayLayout displayLayout, Executor executor) { super(executor); + mContext = context; // Ensure the mBkgBounds is portrait, due to OHM only support on portrait if (displayLayout.height() > displayLayout.width()) { mBkgBounds = new Rect(0, 0, displayLayout.width(), displayLayout.height()); } else { mBkgBounds = new Rect(0, 0, displayLayout.height(), displayLayout.width()); } - final int defaultColor = ContextCompat.getColor(context, R.color.GM2_grey_800); - mDefaultColor = new float[]{Color.red(defaultColor) / 255.0f, - Color.green(defaultColor) / 255.0f, Color.blue(defaultColor) / 255.0f}; + updateThemeColors(); mMainExecutor = executor; mSurfaceControlTransactionFactory = SurfaceControl.Transaction::new; } @@ -170,7 +170,6 @@ public class OneHandedBackgroundPanelOrganizer extends DisplayAreaOrganizer } if (getBackgroundSurface() == null) { - Log.w(TAG, "mBackgroundSurface is null !"); return; } @@ -201,6 +200,30 @@ public class OneHandedBackgroundPanelOrganizer extends DisplayAreaOrganizer } } + /** + * onConfigurationChanged events for updating tutorial text. + */ + public void onConfigurationChanged() { + synchronized (mLock) { + if (mBackgroundSurface == null) { + getBackgroundSurface(); + } else { + removeBackgroundPanelLayer(); + } + updateThemeColors(); + showBackgroundPanelLayer(); + } + } + + private void updateThemeColors() { + synchronized (mLock) { + final int themeColor = mContext.getColor(R.color.one_handed_tutorial_background_color); + mDefaultColor = new float[]{(Color.red(themeColor) - THEME_COLOR_OFFSET) / 255.0f, + (Color.green(themeColor) - THEME_COLOR_OFFSET) / 255.0f, + (Color.blue(themeColor) - THEME_COLOR_OFFSET) / 255.0f}; + } + } + void dump(@NonNull PrintWriter pw) { final String innerPrefix = " "; pw.println(TAG); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java index 2038cff4a966..09cde38a0cfc 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java @@ -658,12 +658,13 @@ public class OneHandedController implements RemoteCallable<OneHandedController> } private void onConfigChanged(Configuration newConfig) { - if (mTutorialHandler == null) { + if (mTutorialHandler == null || mBackgroundPanelOrganizer == null) { return; } if (!mIsOneHandedEnabled || newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) { return; } + mBackgroundPanelOrganizer.onConfigurationChanged(); mTutorialHandler.onConfigurationChanged(); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedTutorialHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedTutorialHandler.java index 0f6c4b081cb7..97e04b5a7abd 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedTutorialHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedTutorialHandler.java @@ -16,6 +16,8 @@ package com.android.wm.shell.onehanded; +import static android.view.View.LAYER_TYPE_HARDWARE; +import static android.view.View.LAYER_TYPE_NONE; import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; import static com.android.wm.shell.onehanded.OneHandedState.STATE_ACTIVE; @@ -23,8 +25,11 @@ import static com.android.wm.shell.onehanded.OneHandedState.STATE_ENTERING; import static com.android.wm.shell.onehanded.OneHandedState.STATE_EXITING; import static com.android.wm.shell.onehanded.OneHandedState.STATE_NONE; +import android.animation.ValueAnimator; import android.annotation.Nullable; import android.content.Context; +import android.content.res.ColorStateList; +import android.content.res.TypedArray; import android.graphics.PixelFormat; import android.graphics.Rect; import android.os.SystemProperties; @@ -33,9 +38,13 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.view.WindowManager; +import android.view.animation.LinearInterpolator; import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.TextView; import androidx.annotation.NonNull; +import androidx.appcompat.view.ContextThemeWrapper; import com.android.internal.annotations.VisibleForTesting; import com.android.wm.shell.R; @@ -45,20 +54,21 @@ import java.io.PrintWriter; /** * Handles tutorial visibility and synchronized transition for One Handed operations, - * TargetViewContainer only be created and attach to window when - * shown counts < {@link MAX_TUTORIAL_SHOW_COUNT}, and detach TargetViewContainer from window - * after exiting one handed mode. + * TargetViewContainer only be created and always attach to window, + * detach TargetViewContainer from window after exiting one handed mode. */ public class OneHandedTutorialHandler implements OneHandedTransitionCallback, OneHandedState.OnStateChangedListener { private static final String TAG = "OneHandedTutorialHandler"; - private static final String ONE_HANDED_MODE_OFFSET_PERCENTAGE = - "persist.debug.one_handed_offset_percentage"; + private static final String OFFSET_PERCENTAGE = "persist.debug.one_handed_offset_percentage"; + private static final String TRANSLATE_ANIMATION_DURATION = + "persist.debug.one_handed_translate_animation_duration"; + private static final float START_TRANSITION_FRACTION = 0.7f; private final float mTutorialHeightRatio; private final WindowManager mWindowManager; + private final OneHandedAnimationCallback mAnimationCallback; - private boolean mIsShowing; private @OneHandedState.State int mCurrentState; private int mTutorialAreaHeight; @@ -67,7 +77,9 @@ public class OneHandedTutorialHandler implements OneHandedTransitionCallback, private @Nullable View mTutorialView; private @Nullable ViewGroup mTargetViewContainer; - private final OneHandedAnimationCallback mAnimationCallback; + private float mAlphaTransitionStart; + private ValueAnimator mAlphaAnimator; + private int mAlphaAnimationDurationMs; public OneHandedTutorialHandler(Context context, WindowManager windowManager) { mContext = context; @@ -75,16 +87,35 @@ public class OneHandedTutorialHandler implements OneHandedTransitionCallback, final float offsetPercentageConfig = context.getResources().getFraction( R.fraction.config_one_handed_offset, 1, 1); final int sysPropPercentageConfig = SystemProperties.getInt( - ONE_HANDED_MODE_OFFSET_PERCENTAGE, Math.round(offsetPercentageConfig * 100.0f)); + OFFSET_PERCENTAGE, Math.round(offsetPercentageConfig * 100.0f)); mTutorialHeightRatio = sysPropPercentageConfig / 100.0f; + final int animationDuration = context.getResources().getInteger( + R.integer.config_one_handed_translate_animation_duration); + mAlphaAnimationDurationMs = SystemProperties.getInt(TRANSLATE_ANIMATION_DURATION, + animationDuration); mAnimationCallback = new OneHandedAnimationCallback() { @Override + public void onOneHandedAnimationCancel( + OneHandedAnimationController.OneHandedTransitionAnimator animator) { + if (mAlphaAnimator != null) { + mAlphaAnimator.cancel(); + } + } + + @Override public void onAnimationUpdate(float xPos, float yPos) { - if (!isShowing()) { + if (!isAttached()) { return; } - mTargetViewContainer.setTransitionGroup(true); - mTargetViewContainer.setTranslationY(yPos - mTargetViewContainer.getHeight()); + if (yPos < mAlphaTransitionStart) { + checkTransitionEnd(); + return; + } + if (mAlphaAnimator == null || mAlphaAnimator.isStarted() + || mAlphaAnimator.isRunning()) { + return; + } + mAlphaAnimator.start(); } }; } @@ -95,13 +126,17 @@ public class OneHandedTutorialHandler implements OneHandedTransitionCallback, switch (newState) { case STATE_ENTERING: createViewAndAttachToWindow(mContext); + updateThemeColor(); + setupAlphaTransition(true /* isEntering */); break; case STATE_ACTIVE: - case STATE_EXITING: - // no - op + checkTransitionEnd(); + setupAlphaTransition(false /* isEntering */); break; + case STATE_EXITING: case STATE_NONE: - removeTutorialFromWindowManager(true /* increment */); + checkTransitionEnd(); + removeTutorialFromWindowManager(); break; default: break; @@ -120,17 +155,20 @@ public class OneHandedTutorialHandler implements OneHandedTransitionCallback, mDisplayBounds = new Rect(0, 0, displayLayout.height(), displayLayout.width()); } mTutorialAreaHeight = Math.round(mDisplayBounds.height() * mTutorialHeightRatio); + mAlphaTransitionStart = mTutorialAreaHeight * START_TRANSITION_FRACTION; } @VisibleForTesting void createViewAndAttachToWindow(Context context) { - if (isShowing()) { + if (isAttached()) { return; } mTutorialView = LayoutInflater.from(context).inflate(R.layout.one_handed_tutorial, null); mTargetViewContainer = new FrameLayout(context); mTargetViewContainer.setClipChildren(false); + mTargetViewContainer.setAlpha(mCurrentState == STATE_ACTIVE ? 1.0f : 0.0f); mTargetViewContainer.addView(mTutorialView); + mTargetViewContainer.setLayerType(LAYER_TYPE_HARDWARE, null); attachTargetToWindow(); } @@ -139,29 +177,27 @@ public class OneHandedTutorialHandler implements OneHandedTransitionCallback, * Adds the tutorial target view to the WindowManager and update its layout. */ private void attachTargetToWindow() { - if (!mTargetViewContainer.isAttachedToWindow()) { - try { - mWindowManager.addView(mTargetViewContainer, getTutorialTargetLayoutParams()); - mIsShowing = true; - } catch (IllegalStateException e) { - // This shouldn't happen, but if the target is already added, just update its - // layout params. - mWindowManager.updateViewLayout( - mTargetViewContainer, getTutorialTargetLayoutParams()); - } + try { + mWindowManager.addView(mTargetViewContainer, getTutorialTargetLayoutParams()); + } catch (IllegalStateException e) { + // This shouldn't happen, but if the target is already added, just update its + // layout params. + mWindowManager.updateViewLayout(mTargetViewContainer, getTutorialTargetLayoutParams()); } } @VisibleForTesting - void removeTutorialFromWindowManager(boolean increment) { - if (mTargetViewContainer != null && mTargetViewContainer.isAttachedToWindow()) { - mWindowManager.removeViewImmediate(mTargetViewContainer); - mIsShowing = false; + void removeTutorialFromWindowManager() { + if (!isAttached()) { + return; } + mTargetViewContainer.setLayerType(LAYER_TYPE_NONE, null); + mWindowManager.removeViewImmediate(mTargetViewContainer); + mTargetViewContainer = null; } @Nullable OneHandedAnimationCallback getAnimationCallback() { - return isShowing() ? mAnimationCallback : null /* Disabled */; + return mAnimationCallback; } /** @@ -183,30 +219,80 @@ public class OneHandedTutorialHandler implements OneHandedTransitionCallback, } @VisibleForTesting - boolean isShowing() { - return mIsShowing; + boolean isAttached() { + return mTargetViewContainer != null && mTargetViewContainer.isAttachedToWindow(); } /** * onConfigurationChanged events for updating tutorial text. */ public void onConfigurationChanged() { - removeTutorialFromWindowManager(false /* increment */); + removeTutorialFromWindowManager(); if (mCurrentState == STATE_ENTERING || mCurrentState == STATE_ACTIVE) { createViewAndAttachToWindow(mContext); + updateThemeColor(); + checkTransitionEnd(); + } + } + + private void updateThemeColor() { + if (mTutorialView == null) { + return; + } + + final Context themedContext = new ContextThemeWrapper(mTutorialView.getContext(), + com.android.internal.R.style.Theme_DeviceDefault_DayNight); + final int textColorPrimary; + final int themedTextColorSecondary; + TypedArray ta = themedContext.obtainStyledAttributes(new int[]{ + com.android.internal.R.attr.textColorPrimary, + com.android.internal.R.attr.textColorSecondary}); + textColorPrimary = ta.getColor(0, 0); + themedTextColorSecondary = ta.getColor(1, 0); + ta.recycle(); + + final ImageView iconView = mTutorialView.findViewById(R.id.one_handed_tutorial_image); + iconView.setImageTintList(ColorStateList.valueOf(textColorPrimary)); + + final TextView tutorialTitle = mTutorialView.findViewById(R.id.one_handed_tutorial_title); + final TextView tutorialDesc = mTutorialView.findViewById( + R.id.one_handed_tutorial_description); + tutorialTitle.setTextColor(textColorPrimary); + tutorialDesc.setTextColor(themedTextColorSecondary); + } + + private void setupAlphaTransition(boolean isEntering) { + final float start = isEntering ? 0.0f : 1.0f; + final float end = isEntering ? 1.0f : 0.0f; + mAlphaAnimator = ValueAnimator.ofFloat(start, end); + mAlphaAnimator.setInterpolator(new LinearInterpolator()); + mAlphaAnimator.setDuration(mAlphaAnimationDurationMs); + mAlphaAnimator.addUpdateListener( + animator -> mTargetViewContainer.setAlpha((float) animator.getAnimatedValue())); + } + + private void checkTransitionEnd() { + if (mAlphaAnimator != null && mAlphaAnimator.isRunning()) { + mAlphaAnimator.end(); + mAlphaAnimator.removeAllUpdateListeners(); + mAlphaAnimator = null; } } void dump(@NonNull PrintWriter pw) { final String innerPrefix = " "; pw.println(TAG); - pw.print(innerPrefix + "mIsShowing="); - pw.println(mIsShowing); + pw.print(innerPrefix + "isAttached="); + pw.println(isAttached()); pw.print(innerPrefix + "mCurrentState="); pw.println(mCurrentState); pw.print(innerPrefix + "mDisplayBounds="); pw.println(mDisplayBounds); pw.print(innerPrefix + "mTutorialAreaHeight="); pw.println(mTutorialAreaHeight); + pw.print(innerPrefix + "mAlphaTransitionStart="); + pw.println(mAlphaTransitionStart); + pw.print(innerPrefix + "mAlphaAnimationDurationMs="); + pw.println(mAlphaAnimationDurationMs); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java index c46b5590bab6..200af7415eb1 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java @@ -26,10 +26,14 @@ import android.animation.RectEvaluator; import android.animation.ValueAnimator; import android.annotation.IntDef; import android.app.TaskInfo; +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Color; import android.graphics.Rect; import android.view.Choreographer; import android.view.Surface; import android.view.SurfaceControl; +import android.view.SurfaceSession; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.graphics.SfVsyncFrameCallbackProvider; @@ -253,6 +257,7 @@ public class PipAnimationController { mSurfaceControlTransactionFactory; private PipSurfaceTransactionHelper mSurfaceTransactionHelper; private @TransitionDirection int mTransitionDirection; + protected SurfaceControl mContentOverlay; private PipTransitionAnimator(TaskInfo taskInfo, SurfaceControl leash, @AnimationType int animationType, Rect destinationBounds, T baseValue, T startValue, @@ -331,6 +336,53 @@ public class PipAnimationController { return false; } + SurfaceControl getContentOverlay() { + return mContentOverlay; + } + + PipTransitionAnimator<T> setUseContentOverlay(Context context) { + final SurfaceControl.Transaction tx = newSurfaceControlTransaction(); + if (mContentOverlay != null) { + // remove existing content overlay if there is any. + tx.remove(mContentOverlay); + tx.apply(); + } + mContentOverlay = new SurfaceControl.Builder(new SurfaceSession()) + .setCallsite("PipAnimation") + .setName("PipContentOverlay") + .setColorLayer() + .build(); + tx.show(mContentOverlay); + tx.setLayer(mContentOverlay, Integer.MAX_VALUE); + tx.setColor(mContentOverlay, getContentOverlayColor(context)); + tx.setAlpha(mContentOverlay, 0f); + tx.reparent(mContentOverlay, mLeash); + tx.apply(); + return this; + } + + private float[] getContentOverlayColor(Context context) { + final TypedArray ta = context.obtainStyledAttributes(new int[] { + android.R.attr.colorBackground }); + try { + int colorAccent = ta.getColor(0, 0); + return new float[] { + Color.red(colorAccent) / 255f, + Color.green(colorAccent) / 255f, + Color.blue(colorAccent) / 255f }; + } finally { + ta.recycle(); + } + } + + /** + * Clears the {@link #mContentOverlay}, this should be done after the content overlay is + * faded out, such as in {@link PipTaskOrganizer#fadeOutAndRemoveOverlay} + */ + void clearContentOverlay() { + mContentOverlay = null; + } + @VisibleForTesting @TransitionDirection public int getTransitionDirection() { return mTransitionDirection; @@ -517,6 +569,9 @@ public class PipAnimationController { final Rect base = getBaseValue(); final Rect start = getStartValue(); final Rect end = getEndValue(); + if (mContentOverlay != null) { + tx.setAlpha(mContentOverlay, fraction < 0.5f ? 0 : (fraction - 0.5f) * 2); + } if (rotatedEndRect != null) { // Animate the bounds in a different orientation. It only happens when // switching between PiP and fullscreen. diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java index f367cd608f37..5a506193b8a1 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java @@ -107,6 +107,13 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, */ private static final int ONE_SHOT_ALPHA_ANIMATION_TIMEOUT_MS = 1000; + /** + * The fixed start delay in ms when fading out the content overlay from bounds animation. + * This is to overcome the flicker caused by configuration change when rotating from landscape + * to portrait PiP in button navigation mode. + */ + private static final int CONTENT_OVERLAY_FADE_OUT_DELAY_MS = 500; + // Not a complete set of states but serves what we want right now. private enum State { UNDEFINED(0), @@ -176,6 +183,10 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, final int direction = animator.getTransitionDirection(); final int animationType = animator.getAnimationType(); final Rect destinationBounds = animator.getDestinationBounds(); + if (isInPipDirection(direction) && animator.getContentOverlay() != null) { + fadeOutAndRemoveOverlay(animator.getContentOverlay(), + animator::clearContentOverlay, true /* withStartDelay*/); + } if (mWaitForFixedRotation && animationType == ANIM_TYPE_BOUNDS && direction == TRANSITION_DIRECTION_TO_PIP) { // Notify the display to continue the deferred orientation change. @@ -199,17 +210,17 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, finishResize(tx, destinationBounds, direction, animationType); sendOnPipTransitionFinished(direction); } - if (direction == TRANSITION_DIRECTION_TO_PIP) { - // TODO (b//169221267): Add jank listener for transactions without buffer updates. - //InteractionJankMonitor.getInstance().end( - // InteractionJankMonitor.CUJ_LAUNCHER_APP_CLOSE_TO_PIP); - } } @Override public void onPipAnimationCancel(TaskInfo taskInfo, PipAnimationController.PipTransitionAnimator animator) { - sendOnPipTransitionCancelled(animator.getTransitionDirection()); + final int direction = animator.getTransitionDirection(); + if (isInPipDirection(direction) && animator.getContentOverlay() != null) { + fadeOutAndRemoveOverlay(animator.getContentOverlay(), + animator::clearContentOverlay, true /* withStartDelay */); + } + sendOnPipTransitionCancelled(direction); } }; @@ -640,7 +651,8 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, // Remove the swipe to home overlay if (swipeToHomeOverlay != null) { - fadeOutAndRemoveOverlay(swipeToHomeOverlay); + fadeOutAndRemoveOverlay(swipeToHomeOverlay, + null /* callback */, false /* withStartDelay */); } }, tx); mInSwipePipToHomeTransition = false; @@ -723,9 +735,12 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, mOnDisplayIdChangeCallback.accept(Display.DEFAULT_DISPLAY); } - final PipAnimationController.PipTransitionAnimator animator = + final PipAnimationController.PipTransitionAnimator<?> animator = mPipAnimationController.getCurrentAnimator(); if (animator != null) { + if (animator.getContentOverlay() != null) { + removeContentOverlay(animator.getContentOverlay(), animator::clearContentOverlay); + } animator.removeAllUpdateListeners(); animator.removeAllListeners(); animator.cancel(); @@ -1196,7 +1211,8 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, snapshotDest); // Start animation to fade out the snapshot. - fadeOutAndRemoveOverlay(snapshotSurface); + fadeOutAndRemoveOverlay(snapshotSurface, + null /* callback */, false /* withStartDelay */); }); } else { applyFinishBoundsResize(wct, direction); @@ -1287,15 +1303,20 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, animator.setTransitionDirection(direction) .setPipAnimationCallback(mPipAnimationCallback) .setPipTransactionHandler(mPipTransactionHandler) - .setDuration(durationMs) - .start(); - if (rotationDelta != Surface.ROTATION_0 && direction == TRANSITION_DIRECTION_TO_PIP) { + .setDuration(durationMs); + if (isInPipDirection(direction)) { + // Similar to auto-enter-pip transition, we use content overlay when there is no + // source rect hint to enter PiP use bounds animation. + if (sourceHintRect == null) animator.setUseContentOverlay(mContext); // The destination bounds are used for the end rect of animation and the final bounds // after animation finishes. So after the animation is started, the destination bounds // can be updated to new rotation (computeRotatedBounds has changed the DisplayLayout // without affecting the animation. - animator.setDestinationBounds(mPipBoundsAlgorithm.getEntryDestinationBounds()); + if (rotationDelta != Surface.ROTATION_0) { + animator.setDestinationBounds(mPipBoundsAlgorithm.getEntryDestinationBounds()); + } } + animator.start(); return animator; } @@ -1308,6 +1329,14 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, outDestinationBounds.set(mPipBoundsAlgorithm.getEntryDestinationBounds()); // Transform the destination bounds to current display coordinates. rotateBounds(outDestinationBounds, displayBounds, mNextRotation, mCurrentRotation); + // When entering PiP (from button navigation mode), adjust the source rect hint by + // display cutout if applicable. + if (sourceHintRect != null && mTaskInfo.displayCutoutInsets != null) { + if (rotationDelta == Surface.ROTATION_270) { + sourceHintRect.offset(mTaskInfo.displayCutoutInsets.left, + mTaskInfo.displayCutoutInsets.top); + } + } } else if (direction == TRANSITION_DIRECTION_LEAVE_PIP) { final Rect rotatedDestinationBounds = new Rect(outDestinationBounds); rotateBounds(rotatedDestinationBounds, mPipBoundsState.getDisplayBounds(), @@ -1346,7 +1375,8 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, /** * Fades out and removes an overlay surface. */ - private void fadeOutAndRemoveOverlay(SurfaceControl surface) { + private void fadeOutAndRemoveOverlay(SurfaceControl surface, Runnable callback, + boolean withStartDelay) { if (surface == null) { return; } @@ -1363,15 +1393,21 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, animator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { - final SurfaceControl.Transaction tx = - mSurfaceControlTransactionFactory.getTransaction(); - tx.remove(surface); - tx.apply(); + removeContentOverlay(surface, callback); } }); + animator.setStartDelay(withStartDelay ? CONTENT_OVERLAY_FADE_OUT_DELAY_MS : 0); animator.start(); } + private void removeContentOverlay(SurfaceControl surface, Runnable callback) { + final SurfaceControl.Transaction tx = + mSurfaceControlTransactionFactory.getTransaction(); + tx.remove(surface); + tx.apply(); + if (callback != null) callback.run(); + } + /** * Dumps internal states. */ diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java index 0bcd1a363eb6..7867f933de4f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java @@ -63,7 +63,6 @@ import java.io.PrintWriter; * the PIP. */ public class PipTouchHandler { - @VisibleForTesting static final float MINIMUM_SIZE_PERCENT = 0.4f; private static final String TAG = "PipTouchHandler"; private static final float DEFAULT_STASH_VELOCITY_THRESHOLD = 18000.f; @@ -115,6 +114,7 @@ public class PipTouchHandler { private float mSavedSnapFraction = -1f; private boolean mSendingHoverAccessibilityEvents; private boolean mMovementWithinDismiss; + private float mMinimumSizePercent; // Touch state private final PipTouchState mTouchState; @@ -252,6 +252,7 @@ public class PipTouchHandler { mExpandedShortestEdgeSize = res.getDimensionPixelSize( R.dimen.pip_expanded_shortest_edge_size); mImeOffset = res.getDimensionPixelSize(R.dimen.pip_ime_offset); + mMinimumSizePercent = res.getFraction(R.fraction.config_pipShortestEdgePercent, 1, 1); mPipDismissTargetHandler.updateMagneticTargetSize(); } @@ -481,12 +482,12 @@ public class PipTouchHandler { + (mPipBoundsState.getDisplayBounds().height() - insetBounds.bottom); final int minWidth, minHeight, maxWidth, maxHeight; if (aspectRatio > 1f) { - minWidth = (int) Math.min(normalBounds.width(), shorterLength * MINIMUM_SIZE_PERCENT); + minWidth = (int) Math.min(normalBounds.width(), shorterLength * mMinimumSizePercent); minHeight = (int) (minWidth / aspectRatio); maxWidth = (int) Math.max(normalBounds.width(), shorterLength - totalHorizontalPadding); maxHeight = (int) (maxWidth / aspectRatio); } else { - minHeight = (int) Math.min(normalBounds.height(), shorterLength * MINIMUM_SIZE_PERCENT); + minHeight = (int) Math.min(normalBounds.height(), shorterLength * mMinimumSizePercent); minWidth = (int) (minHeight * aspectRatio); maxHeight = (int) Math.max(normalBounds.height(), shorterLength - totalVerticalPadding); maxWidth = (int) (maxHeight * aspectRatio); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java index 107a3f880354..56ad2be11853 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java @@ -37,6 +37,7 @@ import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Rect; import android.graphics.drawable.AdaptiveIconDrawable; +import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.graphics.drawable.LayerDrawable; @@ -57,6 +58,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.graphics.palette.Palette; import com.android.internal.graphics.palette.Quantizer; import com.android.internal.graphics.palette.VariationalKMeansQuantizer; +import com.android.launcher3.icons.BaseIconFactory; import com.android.launcher3.icons.IconProvider; import com.android.wm.shell.common.TransactionPool; @@ -368,8 +370,14 @@ public class SplashscreenContentDrawer { if (DEBUG) { Slog.d(TAG, "The icon is not an AdaptiveIconDrawable"); } - // TODO process legacy icon(bitmap) - createIconDrawable(iconDrawable, true); + Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "legacy_icon_factory"); + final ShapeIconFactory factory = new ShapeIconFactory( + SplashscreenContentDrawer.this.mContext, + scaledIconDpi, mFinalIconSize); + final Bitmap bitmap = factory.createScaledBitmapWithoutShadow( + iconDrawable, true /* shrinkNonAdaptiveIcons */); + Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); + createIconDrawable(new BitmapDrawable(bitmap), true); } animationDuration = 0; } @@ -377,11 +385,15 @@ public class SplashscreenContentDrawer { return fillViewWithIcon(mFinalIconSize, mFinalIconDrawable, animationDuration); } + private class ShapeIconFactory extends BaseIconFactory { + protected ShapeIconFactory(Context context, int fillResIconDpi, int iconBitmapSize) { + super(context, fillResIconDpi, iconBitmapSize, true /* shapeDetection */); + } + } + private void createIconDrawable(Drawable iconDrawable, boolean legacy) { if (legacy) { mFinalIconDrawable = SplashscreenIconDrawableFactory.makeLegacyIconDrawable( - mTmpAttrs.mIconBgColor != Color.TRANSPARENT - ? mTmpAttrs.mIconBgColor : Color.WHITE, iconDrawable, mDefaultIconSize, mFinalIconSize, mSplashscreenWorkerHandler); } else { mFinalIconDrawable = SplashscreenIconDrawableFactory.makeIconDrawable( diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenIconDrawableFactory.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenIconDrawableFactory.java index 211941f44c8e..ba9123dca999 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenIconDrawableFactory.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenIconDrawableFactory.java @@ -64,11 +64,10 @@ public class SplashscreenIconDrawableFactory { } } - static Drawable makeLegacyIconDrawable(@ColorInt int backgroundColor, - @NonNull Drawable foregroundDrawable, int srcIconSize, int iconSize, - Handler splashscreenWorkerHandler) { - return new ImmobileIconDrawable(new LegacyIconDrawable(backgroundColor, - foregroundDrawable), srcIconSize, iconSize, splashscreenWorkerHandler); + static Drawable makeLegacyIconDrawable(@NonNull Drawable iconDrawable, int srcIconSize, + int iconSize, Handler splashscreenWorkerHandler) { + return new ImmobileIconDrawable(iconDrawable, srcIconSize, iconSize, + splashscreenWorkerHandler); } private static class ImmobileIconDrawable extends Drawable { @@ -179,65 +178,6 @@ public class SplashscreenIconDrawableFactory { } } - private static class LegacyIconDrawable extends MaskBackgroundDrawable { - // reference FixedScaleDrawable - // iconBounds = 0.7 * X * outerBounds, X is the scale of diagonal - private static final float LEGACY_ICON_SCALE = .7f * .8f; - private final Drawable mForegroundDrawable; - private float mScaleX, mScaleY, mTransX, mTransY; - - LegacyIconDrawable(@ColorInt int backgroundColor, Drawable foregroundDrawable) { - super(backgroundColor); - mForegroundDrawable = foregroundDrawable; - mScaleX = LEGACY_ICON_SCALE; - mScaleY = LEGACY_ICON_SCALE; - } - - @Override - protected void updateLayerBounds(Rect bounds) { - super.updateLayerBounds(bounds); - - if (mForegroundDrawable == null) { - return; - } - float outerBoundsWidth = bounds.width(); - float outerBoundsHeight = bounds.height(); - float h = mForegroundDrawable.getIntrinsicHeight(); - float w = mForegroundDrawable.getIntrinsicWidth(); - mScaleX = LEGACY_ICON_SCALE; - mScaleY = LEGACY_ICON_SCALE; - if (h > w && w > 0) { - mScaleX *= w / h; - } else if (w > h && h > 0) { - mScaleY *= h / w; - } - int innerBoundsWidth = (int) (0.5 + outerBoundsWidth * mScaleX); - int innerBoundsHeight = (int) (0.5 + outerBoundsHeight * mScaleY); - final Rect rect = new Rect(0, 0, innerBoundsWidth, innerBoundsHeight); - mForegroundDrawable.setBounds(rect); - mTransX = (outerBoundsWidth - innerBoundsWidth) / 2; - mTransY = (outerBoundsHeight - innerBoundsHeight) / 2; - invalidateSelf(); - } - - @Override - public void draw(Canvas canvas) { - super.draw(canvas); - int saveCount = canvas.save(); - canvas.translate(mTransX, mTransY); - if (mForegroundDrawable != null) { - mForegroundDrawable.draw(canvas); - } - canvas.restoreToCount(saveCount); - } - - @Override - public void setColorFilter(ColorFilter colorFilter) { - if (mForegroundDrawable != null) { - mForegroundDrawable.setColorFilter(colorFilter); - } - } - } /** * A lightweight AdaptiveIconDrawable which support foreground to be Animatable, and keep this * drawable masked by config_icon_mask. diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedTutorialHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedTutorialHandlerTest.java index 25bdb8ef9263..ae1d3b2a4c41 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedTutorialHandlerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedTutorialHandlerTest.java @@ -77,7 +77,7 @@ public class OneHandedTutorialHandlerTest extends OneHandedTestCase { @Test public void testOnStateChangedEntering_createViewAndAttachToWindow() { - when(mSpiedTutorialHandler.isShowing()).thenReturn(true); + when(mSpiedTutorialHandler.isAttached()).thenReturn(true); try { mSpiedTutorialHandler.onStateChanged(STATE_ENTERING); } catch (ClassCastException e) { @@ -89,23 +89,27 @@ public class OneHandedTutorialHandlerTest extends OneHandedTestCase { @Test public void testOnStateChangedNone_removeViewAndAttachToWindow() { - when(mSpiedTutorialHandler.isShowing()).thenReturn(true); + when(mSpiedTutorialHandler.isAttached()).thenReturn(true); try { mSpiedTutorialHandler.onStateChanged(STATE_NONE); } catch (ClassCastException e) { // no-op, just assert removeTutorialFromWindowManager() to be called + } catch (NullPointerException e) { + // no-op, just assert removeTutorialFromWindowManager() to be called } - verify(mSpiedTutorialHandler).removeTutorialFromWindowManager(true); + verify(mSpiedTutorialHandler).removeTutorialFromWindowManager(); } @Test public void testOnStateChangedNone_shouldNotAttachWindow() { - when(mSpiedTutorialHandler.isShowing()).thenReturn(true); + when(mSpiedTutorialHandler.isAttached()).thenReturn(true); try { mSpiedTutorialHandler.onStateChanged(STATE_NONE); } catch (ClassCastException e) { // no-op, just assert setTutorialShownCountIncrement() never be called + } catch (NullPointerException e) { + // no-op, just assert setTutorialShownCountIncrement() never be called } verify(mSpiedTutorialHandler, never()).createViewAndAttachToWindow(any()); @@ -113,7 +117,7 @@ public class OneHandedTutorialHandlerTest extends OneHandedTestCase { @Test public void testOnConfigurationChanged_shouldUpdateViewContent() { - when(mSpiedTutorialHandler.isShowing()).thenReturn(true); + when(mSpiedTutorialHandler.isAttached()).thenReturn(true); try { mSpiedTutorialHandler.onStateChanged(STATE_ENTERING); } catch (ClassCastException e) { @@ -122,9 +126,12 @@ public class OneHandedTutorialHandlerTest extends OneHandedTestCase { try { mSpiedTutorialHandler.onConfigurationChanged(); } catch (ClassCastException e) { - // no-op, just assert removeTutorialFromWindowManager() be called + } catch (NullPointerException e) { + // no-op, just assert removeTutorialFromWindowManager() be called, + // and createViewAndAttachToWindow() be called twice } - verify(mSpiedTutorialHandler).removeTutorialFromWindowManager(false); + verify(mSpiedTutorialHandler).createViewAndAttachToWindow(any()); + verify(mSpiedTutorialHandler).removeTutorialFromWindowManager(); } } |