Don't change View visibility during activity transitions.

Bug 16187776

Changing View visibility can change the View's focused item.
To prevent this, a backdoor is introduced into Transition
and Visiblity to set the target Visibility used in Activity Transitions.

Change-Id: Idfd2c6fba2cad80fecdfd086990ddc604f86ca68
diff --git a/core/java/android/app/ActivityTransitionCoordinator.java b/core/java/android/app/ActivityTransitionCoordinator.java
index 1a03fe9..15be9b1 100644
--- a/core/java/android/app/ActivityTransitionCoordinator.java
+++ b/core/java/android/app/ActivityTransitionCoordinator.java
@@ -280,14 +280,6 @@
 
     public ArrayList<String> getAllSharedElementNames() { return mAllSharedElementNames; }
 
-    public static void setViewVisibility(Collection<View> views, int visibility) {
-        if (views != null) {
-            for (View view : views) {
-                view.setVisibility(visibility);
-            }
-        }
-    }
-
     protected Transition setTargets(Transition transition, boolean add) {
         if (transition == null || (add &&
                 (mTransitioningViews == null || mTransitioningViews.isEmpty()))) {
@@ -531,6 +523,13 @@
         return getWindow().getTransitionBackgroundFadeDuration();
     }
 
+    protected static void setTransitionAlpha(ArrayList<View> views, float alpha) {
+        int numSharedElements = views.size();
+        for (int i = 0; i < numSharedElements; i++) {
+            views.get(i).setTransitionAlpha(alpha);
+        }
+    }
+
     /**
      * Captures placement information for Views with a shared element name for
      * Activity Transitions.
diff --git a/core/java/android/app/EnterTransitionCoordinator.java b/core/java/android/app/EnterTransitionCoordinator.java
index 90f36b8..f50c93b 100644
--- a/core/java/android/app/EnterTransitionCoordinator.java
+++ b/core/java/android/app/EnterTransitionCoordinator.java
@@ -104,9 +104,9 @@
             mHandler.sendEmptyMessageDelayed(MSG_CANCEL, MAX_WAIT_MS);
             send(MSG_SEND_SHARED_ELEMENT_DESTINATION, null);
         }
-        setViewVisibility(mSharedElements, View.INVISIBLE);
+        setTransitionAlpha(mSharedElements, 0);
         if (getViewsTransition() != null) {
-            setViewVisibility(mTransitioningViews, View.INVISIBLE);
+            setTransitionAlpha(mTransitioningViews, 0);
         }
         if (mSharedElementsBundle != null) {
             onTakeSharedElements();
@@ -221,7 +221,7 @@
         if (!mIsCanceled) {
             mIsCanceled = true;
             if (getViewsTransition() == null || mIsViewsTransitionStarted) {
-                setViewVisibility(mSharedElements, View.VISIBLE);
+                setTransitionAlpha(mSharedElements, 1);
             } else {
                 mTransitioningViews.addAll(mSharedElements);
             }
@@ -281,7 +281,7 @@
         // Now start shared element transition
         ArrayList<View> sharedElementSnapshots = createSnapshots(sharedElementState,
                 mSharedElementNames);
-        setViewVisibility(mSharedElements, View.VISIBLE);
+        setTransitionAlpha(mSharedElements, 1);
         ArrayMap<ImageView, Pair<ImageView.ScaleType, Matrix>> originalImageViewState =
                 setSharedElementState(sharedElementState, sharedElementSnapshots);
         requestLayoutForSharedElements();
@@ -314,13 +314,6 @@
         }
     }
 
-    @Override
-    protected void stripOffscreenViews() {
-        setViewVisibility(mTransitioningViews, View.VISIBLE);
-        super.stripOffscreenViews();
-        setViewVisibility(mTransitioningViews, View.INVISIBLE);
-    }
-
     private void onTakeSharedElements() {
         if (!mIsReadyForTransition || mSharedElementsBundle == null) {
             return;
@@ -362,6 +355,8 @@
             viewsTransition = configureTransition(getViewsTransition(), true);
             if (viewsTransition != null) {
                 stripOffscreenViews();
+                viewsTransition.forceVisibility(View.INVISIBLE, true);
+                setTransitionAlpha(mTransitioningViews, 1);
             }
         }
         mIsViewsTransitionStarted = mIsViewsTransitionStarted || startEnterTransition;
@@ -405,7 +400,6 @@
     }
 
     private void startEnterTransition(Transition transition) {
-        setViewVisibility(mTransitioningViews, View.VISIBLE);
         if (!mIsReturning) {
             Drawable background = getDecor().getBackground();
             if (background != null) {
diff --git a/core/java/android/app/ExitTransitionCoordinator.java b/core/java/android/app/ExitTransitionCoordinator.java
index 0fbd685..3f3e00c 100644
--- a/core/java/android/app/ExitTransitionCoordinator.java
+++ b/core/java/android/app/ExitTransitionCoordinator.java
@@ -68,6 +68,8 @@
 
     private boolean mIsExitStarted;
 
+    private boolean mSharedElementsHidden;
+
     public ExitTransitionCoordinator(Activity activity, ArrayList<String> names,
             ArrayList<String> accepted, ArrayList<View> mapped, boolean isReturning) {
         super(activity.getWindow(), names, getListener(activity, isReturning),
@@ -113,8 +115,8 @@
     }
 
     public void resetViews() {
-        setViewVisibility(mTransitioningViews, View.VISIBLE);
-        setViewVisibility(mSharedElements, View.VISIBLE);
+        setTransitionAlpha(mTransitioningViews, 1);
+        setTransitionAlpha(mSharedElements, 1);
         mIsHidden = true;
         clearState();
     }
@@ -161,8 +163,9 @@
 
     private void hideSharedElements() {
         if (!mIsHidden) {
-            setViewVisibility(mSharedElements, View.INVISIBLE);
+            setTransitionAlpha(mSharedElements, 0);
         }
+        mSharedElementsHidden = true;
         finishIfNecessary();
     }
 
@@ -175,7 +178,6 @@
                     beginTransitions();
                 }
             });
-            setViewVisibility(mTransitioningViews, View.INVISIBLE);
         }
     }
 
@@ -219,7 +221,7 @@
         Transition transition = getExitTransition();
         if (transition != null) {
             TransitionManager.beginDelayedTransition(getDecor(), transition);
-            setViewVisibility(mTransitioningViews, View.INVISIBLE);
+            mTransitioningViews.get(0).invalidate();
         }
     }
 
@@ -241,6 +243,8 @@
                 });
                 mBackgroundAnimator.setDuration(getFadeDuration());
                 mBackgroundAnimator.start();
+            } else {
+                mIsBackgroundReady = true;
             }
         }
     }
@@ -259,7 +263,7 @@
                     transition.removeListener(this);
                     exitTransitionComplete();
                     if (mIsHidden) {
-                        setViewVisibility(mTransitioningViews, View.VISIBLE);
+                        setTransitionAlpha(mTransitioningViews, 1);
                     }
                 }
 
@@ -268,6 +272,7 @@
                     super.onTransitionCancel(transition);
                 }
             });
+            viewsTransition.forceVisibility(View.INVISIBLE, false);
         }
         return viewsTransition;
     }
@@ -286,7 +291,7 @@
                     transition.removeListener(this);
                     sharedElementTransitionComplete();
                     if (mIsHidden) {
-                        setViewVisibility(mSharedElements, View.VISIBLE);
+                        setTransitionAlpha(mSharedElements, 1);
                     }
                 }
             });
@@ -302,6 +307,7 @@
         Transition transition = mergeTransitions(sharedElementTransition, viewsTransition);
         if (transition != null) {
             TransitionManager.beginDelayedTransition(getDecor(), transition);
+            getDecor().invalidate();
         }
     }
 
@@ -353,7 +359,7 @@
 
     private void finishIfNecessary() {
         if (mIsReturning && mExitNotified && mActivity != null && (mSharedElements.isEmpty() ||
-                mSharedElements.get(0).getVisibility() == View.INVISIBLE)) {
+                mSharedElementsHidden)) {
             finish();
         }
         if (!mIsReturning && mExitNotified) {
diff --git a/core/java/android/transition/ChangeBounds.java b/core/java/android/transition/ChangeBounds.java
index 2591e24..3f5e8e8 100644
--- a/core/java/android/transition/ChangeBounds.java
+++ b/core/java/android/transition/ChangeBounds.java
@@ -310,7 +310,8 @@
                 Canvas canvas = new Canvas(bitmap);
                 view.draw(canvas);
                 final BitmapDrawable drawable = new BitmapDrawable(bitmap);
-                view.setVisibility(View.INVISIBLE);
+                final float transitionAlpha = view.getTransitionAlpha();
+                view.setTransitionAlpha(0);
                 sceneRoot.getOverlay().add(drawable);
                 Path topLeftPath = getPathMotion().getPath(startX - tempLocation[0],
                         startY - tempLocation[1], endX - tempLocation[0], endY - tempLocation[1]);
@@ -321,7 +322,7 @@
                     @Override
                     public void onAnimationEnd(Animator animation) {
                         sceneRoot.getOverlay().remove(drawable);
-                        view.setVisibility(View.VISIBLE);
+                        view.setTransitionAlpha(transitionAlpha);
                     }
                 });
                 return anim;
diff --git a/core/java/android/transition/Transition.java b/core/java/android/transition/Transition.java
index c14678a..1967213 100644
--- a/core/java/android/transition/Transition.java
+++ b/core/java/android/transition/Transition.java
@@ -2037,6 +2037,9 @@
         return mNameOverrides;
     }
 
+    /** @hide */
+    public void forceVisibility(int visibility, boolean isStartValue) {}
+
     @Override
     public String toString() {
         return toString("");
diff --git a/core/java/android/transition/TransitionSet.java b/core/java/android/transition/TransitionSet.java
index 63957e9..c6005b9 100644
--- a/core/java/android/transition/TransitionSet.java
+++ b/core/java/android/transition/TransitionSet.java
@@ -293,6 +293,15 @@
         }
     }
 
+    /** @hide */
+    @Override
+    public void forceVisibility(int visibility, boolean isStartValue) {
+        int numTransitions = mTransitions.size();
+        for (int i = 0; i < numTransitions; i++) {
+            mTransitions.get(i).forceVisibility(visibility, isStartValue);
+        }
+    }
+
     /**
      * Removes the specified child transition from this set.
      *
diff --git a/core/java/android/transition/Visibility.java b/core/java/android/transition/Visibility.java
index 1629726..cf5ea4c 100644
--- a/core/java/android/transition/Visibility.java
+++ b/core/java/android/transition/Visibility.java
@@ -78,6 +78,9 @@
 
     private int mMode = IN | OUT;
 
+    private int mForcedStartVisibility = -1;
+    private int mForcedEndVisibility = -1;
+
     public Visibility() {}
 
     public Visibility(Context context, AttributeSet attrs) {
@@ -121,8 +124,13 @@
         return sTransitionProperties;
     }
 
-    private void captureValues(TransitionValues transitionValues) {
-        int visibility = transitionValues.view.getVisibility();
+    private void captureValues(TransitionValues transitionValues, int forcedVisibility) {
+        int visibility;
+        if (forcedVisibility != -1) {
+            visibility = forcedVisibility;
+        } else {
+            visibility = transitionValues.view.getVisibility();
+        }
         transitionValues.values.put(PROPNAME_VISIBILITY, visibility);
         transitionValues.values.put(PROPNAME_PARENT, transitionValues.view.getParent());
         int[] loc = new int[2];
@@ -132,12 +140,22 @@
 
     @Override
     public void captureStartValues(TransitionValues transitionValues) {
-        captureValues(transitionValues);
+        captureValues(transitionValues, mForcedStartVisibility);
     }
 
     @Override
     public void captureEndValues(TransitionValues transitionValues) {
-        captureValues(transitionValues);
+        captureValues(transitionValues, mForcedEndVisibility);
+    }
+
+    /** @hide */
+    @Override
+    public void forceVisibility(int visibility, boolean isStartValue) {
+        if (isStartValue) {
+            mForcedStartVisibility = visibility;
+        } else {
+            mForcedEndVisibility = visibility;
+        }
     }
 
     /**
@@ -402,26 +420,29 @@
         }
 
         if (viewToKeep != null) {
-            int originalVisibility = viewToKeep.getVisibility();
-            viewToKeep.setVisibility(View.VISIBLE);
+            int originalVisibility = -1;
+            final boolean isForcedVisibility = mForcedStartVisibility != -1 ||
+                    mForcedEndVisibility != -1;
+            if (!isForcedVisibility) {
+                originalVisibility = viewToKeep.getVisibility();
+                viewToKeep.setVisibility(View.VISIBLE);
+            }
             Animator animator = onDisappear(sceneRoot, viewToKeep, startValues, endValues);
-            if (animator == null) {
-                viewToKeep.setVisibility(originalVisibility);
-            } else {
+            if (animator != null) {
                 final View finalViewToKeep = viewToKeep;
                 animator.addListener(new AnimatorListenerAdapter() {
                     boolean mCanceled = false;
 
                     @Override
                     public void onAnimationPause(Animator animation) {
-                        if (!mCanceled) {
+                        if (!mCanceled && !isForcedVisibility) {
                             finalViewToKeep.setVisibility(finalVisibility);
                         }
                     }
 
                     @Override
                     public void onAnimationResume(Animator animation) {
-                        if (!mCanceled) {
+                        if (!mCanceled && !isForcedVisibility) {
                             finalViewToKeep.setVisibility(View.VISIBLE);
                         }
                     }
@@ -434,10 +455,16 @@
                     @Override
                     public void onAnimationEnd(Animator animation) {
                         if (!mCanceled) {
-                            finalViewToKeep.setVisibility(finalVisibility);
+                            if (isForcedVisibility) {
+                                finalViewToKeep.setTransitionAlpha(0);
+                            } else {
+                                finalViewToKeep.setVisibility(finalVisibility);
+                            }
                         }
                     }
                 });
+            } else if (!isForcedVisibility) {
+                viewToKeep.setVisibility(originalVisibility);
             }
             return animator;
         }