content: /*
   * Copyright (C) 2014 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 android.app;
  
  import android.animation.Animator;
  import android.animation.AnimatorListenerAdapter;
  import android.animation.ObjectAnimator;
  import android.app.SharedElementCallback.OnSharedElementsReadyListener;
  import android.graphics.Color;
  import android.graphics.drawable.ColorDrawable;
  import android.graphics.drawable.Drawable;
  import android.os.Bundle;
  import android.os.ResultReceiver;
  import android.text.TextUtils;
  import android.transition.Transition;
  import android.transition.TransitionListenerAdapter;
  import android.transition.TransitionManager;
  import android.util.ArrayMap;
  import android.view.View;
  import android.view.ViewGroup;
  import android.view.ViewGroupOverlay;
  import android.view.ViewTreeObserver;
  import android.view.Window;
  import android.view.accessibility.AccessibilityEvent;
  
  import com.android.internal.view.OneShotPreDrawListener;
  
  import java.util.ArrayList;
  
  /**
   * This ActivityTransitionCoordinator is created by the Activity to manage
   * the enter scene and shared element transfer into the Scene, either during
   * launch of an Activity or returning from a launched Activity.
   */
  class EnterTransitionCoordinator extends ActivityTransitionCoordinator {
      private static final String TAG = "EnterTransitionCoordinator";
  
      private static final int MIN_ANIMATION_FRAMES = 2;
  
      private boolean mSharedElementTransitionStarted;
      private Activity mActivity;
      private boolean mHasStopped;
      private boolean mIsCanceled;
      private ObjectAnimator mBackgroundAnimator;
      private boolean mIsExitTransitionComplete;
      private boolean mIsReadyForTransition;
      private Bundle mSharedElementsBundle;
      private boolean mWasOpaque;
      private boolean mAreViewsReady;
      private boolean mIsViewsTransitionStarted;
      private Transition mEnterViewsTransition;
      private OneShotPreDrawListener mViewsReadyListener;
      private final boolean mIsCrossTask;
      private Drawable mReplacedBackground;
      private ArrayList<String> mPendingExitNames;
  
      public EnterTransitionCoordinator(Activity activity, ResultReceiver resultReceiver,
              ArrayList<String> sharedElementNames, boolean isReturning, boolean isCrossTask) {
          super(activity.getWindow(), sharedElementNames,
                  getListener(activity, isReturning && !isCrossTask), isReturning);
          mActivity = activity;
          mIsCrossTask = isCrossTask;
          setResultReceiver(resultReceiver);
          prepareEnter();
          Bundle resultReceiverBundle = new Bundle();
          resultReceiverBundle.putParcelable(KEY_REMOTE_RECEIVER, this);
          mResultReceiver.send(MSG_SET_REMOTE_RECEIVER, resultReceiverBundle);
          final View decorView = getDecor();
          if (decorView != null) {
              final ViewTreeObserver viewTreeObserver = decorView.getViewTreeObserver();
              viewTreeObserver.addOnPreDrawListener(
                      new ViewTreeObserver.OnPreDrawListener() {
                          @Override
                          public boolean onPreDraw() {
                              if (mIsReadyForTransition) {
                                  if (viewTreeObserver.isAlive()) {
                                      viewTreeObserver.removeOnPreDrawListener(this);
                                  } else {
                                      decorView.getViewTreeObserver().removeOnPreDrawListener(this);
                                  }
                              }
                              return false;
                          }
                      });
          }
      }
  
      boolean isCrossTask() {
          return mIsCrossTask;
      }
  
      public void viewInstancesReady(ArrayList<String> accepted, ArrayList<String> localNames,
              ArrayList<View> localViews) {
          boolean remap = false;
          for (int i = 0; i < localViews.size(); i++) {
              View view = localViews.get(i);
              if (!TextUtils.equals(view.getTransitionName(), localNames.get(i))
                      || !view.isAttachedToWindow()) {
                  remap = true;
                  break;
              }
          }
          if (remap) {
              triggerViewsReady(mapNamedElements(accepted, localNames));
          } else {
              triggerViewsReady(mapSharedElements(accepted, localViews));
          }
      }
  
      public void namedViewsReady(ArrayList<String> accepted, ArrayList<String> localNames) {
          triggerViewsReady(mapNamedElements(accepted, localNames));
      }
  
      public Transition getEnterViewsTransition() {
          return mEnterViewsTransition;
      }
  
      @Override
      protected void viewsReady(ArrayMap<String, View> sharedElements) {
          super.viewsReady(sharedElements);
          mIsReadyForTransition = true;
          hideViews(mSharedElements);
          Transition viewsTransition = getViewsTransition();
          if (viewsTransition != null && mTransitioningViews != null) {
              removeExcludedViews(viewsTransition, mTransitioningViews);
              stripOffscreenViews();
              hideViews(mTransitioningViews);
          }
          if (mIsReturning) {
              sendSharedElementDestination();
          } else {
              moveSharedElementsToOverlay();
          }
          if (mSharedElementsBundle != null) {
              onTakeSharedElements();
          }
      }
  
      private void triggerViewsReady(final ArrayMap<String, View> sharedElements) {
          if (mAreViewsReady) {
              return;
          }
          mAreViewsReady = true;
          final ViewGroup decor = getDecor();
          // Ensure the views have been laid out before capturing the views -- we need the epicenter.
          if (decor == null || (decor.isAttachedToWindow() &&
                  (sharedElements.isEmpty() || !sharedElements.valueAt(0).isLayoutRequested()))) {
              viewsReady(sharedElements);
          } else {
              mViewsReadyListener = OneShotPreDrawListener.add(decor, () -> {
                  mViewsReadyListener = null;
                  viewsReady(sharedElements);
              });
              decor.invalidate();
          }
      }
  
      private ArrayMap<String, View> mapNamedElements(ArrayList<String> accepted,
              ArrayList<String> localNames) {
          ArrayMap<String, View> sharedElements = new ArrayMap<String, View>();
          ViewGroup decorView = getDecor();
          if (decorView != null) {
              decorView.findNamedViews(sharedElements);
          }
          if (accepted != null) {
              for (int i = 0; i < localNames.size(); i++) {
                  String localName = localNames.get(i);
                  String acceptedName = accepted.get(i);
                  if (localName != null && !localName.equals(acceptedName)) {
                      View view = sharedElements.get(localName);
                      if (view != null) {
                          sharedElements.put(acceptedName, view);
                      }
                  }
              }
          }
          return sharedElements;
      }
  
      private void sendSharedElementDestination() {
          boolean allReady;
          final View decorView = getDecor();
          if (allowOverlappingTransitions() && getEnterViewsTransition() != null) {
              allReady = false;
          } else if (decorView == null) {
              allReady = true;
          } else {
              allReady = !decorView.isLayoutRequested();
              if (allReady) {
                  for (int i = 0; i < mSharedElements.size(); i++) {
                      if (mSharedElements.get(i).isLayoutRequested()) {
                          allReady = false;
                          break;
                      }
                  }
              }
          }
          if (allReady) {
              Bundle state = captureSharedElementState();
              moveSharedElementsToOverlay();
              mResultReceiver.send(MSG_SHARED_ELEMENT_DESTINATION, state);
          } else if (decorView != null) {
              OneShotPreDrawListener.add(decorView, () -> {
                  if (mResultReceiver != null) {
                      Bundle state = captureSharedElementState();
                      moveSharedElementsToOverlay();
                      mResultReceiver.send(MSG_SHARED_ELEMENT_DESTINATION, state);
                  }
              });
          }
          if (allowOverlappingTransitions()) {
              startEnterTransitionOnly();
          }
      }
  
      private static SharedElementCallback getListener(Activity activity, boolean isReturning) {
          return isReturning ? activity.mExitTransitionListener : activity.mEnterTransitionListener;
      }
  
      @Override
      protected void onReceiveResult(int resultCode, Bundle resultData) {
          switch (resultCode) {
              case MSG_TAKE_SHARED_ELEMENTS:
                  if (!mIsCanceled) {
                      mSharedElementsBundle = resultData;
                      onTakeSharedElements();
                  }
                  break;
              case MSG_EXIT_TRANSITION_COMPLETE:
                  if (!mIsCanceled) {
                      mIsExitTransitionComplete = true;
                      if (mSharedElementTransitionStarted) {
                          onRemoteExitTransitionComplete();
                      }
                  }
                  break;
              case MSG_CANCEL:
                  cancel();
                  break;
              case MSG_ALLOW_RETURN_TRANSITION:
                  if (!mIsCanceled) {
                      mPendingExitNames = mAllSharedElementNames;
                  }
                  break;
          }
      }
  
      public boolean isWaitingForRemoteExit() {
          return mIsReturning && mResultReceiver != null;
      }
  
      public ArrayList<String> getPendingExitSharedElementNames() {
          return mPendingExitNames;
      }
  
      /**
       * This is called onResume. If an Activity is resuming and the transitions
       * haven't started yet, force the views to appear. This is likely to be
       * caused by the top Activity finishing before the transitions started.
       * In that case, we can finish any transition that was started, but we
       * should cancel any pending transition and just bring those Views visible.
       */
      public void forceViewsToAppear() {
          if (!mIsReturning) {
              return;
          }
          if (!mIsReadyForTransition) {
              mIsReadyForTransition = true;
              final ViewGroup decor = getDecor();
              if (decor != null && mViewsReadyListener != null) {
                  mViewsReadyListener.removeListener();
                  mViewsReadyListener = null;
              }
              showViews(mTransitioningViews, true);
              setTransitioningViewsVisiblity(View.VISIBLE, true);
              mSharedElements.clear();
              mAllSharedElementNames.clear();
              mTransitioningViews.clear();
              mIsReadyForTransition = true;
              viewsTransitionComplete();
              sharedElementTransitionComplete();
          } else {
              if (!mSharedElementTransitionStarted) {
                  moveSharedElementsFromOverlay();
                  mSharedElementTransitionStarted = true;
                  showViews(mSharedElements, true);
                  mSharedElements.clear();
                  sharedElementTransitionComplete();
              }
              if (!mIsViewsTransitionStarted) {
                  mIsViewsTransitionStarted = true;
                  showViews(mTransitioningViews, true);
                  setTransitioningViewsVisiblity(View.VISIBLE, true);
                  mTransitioningViews.clear();
                  viewsTransitionComplete();
              }
              cancelPendingTransitions();
          }
          mAreViewsReady = true;
          if (mResultReceiver != null) {
              mResultReceiver.send(MSG_CANCEL, null);
              mResultReceiver = null;
          }
      }
  
      private void cancel() {
          if (!mIsCanceled) {
              mIsCanceled = true;
              if (getViewsTransition() == null || mIsViewsTransitionStarted) {
                  showViews(mSharedElements, true);
              } else if (mTransitioningViews != null) {
                  mTransitioningViews.addAll(mSharedElements);
              }
              moveSharedElementsFromOverlay();
              mSharedElementNames.clear();
              mSharedElements.clear();
              mAllSharedElementNames.clear();
              startSharedElementTransition(null);
              onRemoteExitTransitionComplete();
          }
      }
  
      public boolean isReturning() {
          return mIsReturning;
      }
  
      protected void prepareEnter() {
          ViewGroup decorView = getDecor();
          if (mActivity == null || decorView == null) {
              return;
          }
          if (!isCrossTask()) {
              mActivity.overridePendingTransition(0, 0);
          }
          if (!mIsReturning) {
              mWasOpaque = mActivity.convertToTranslucent(null, null);
              Drawable background = decorView.getBackground();
              if (background == null) {
                  background = new ColorDrawable(Color.TRANSPARENT);
                  mReplacedBackground = background;
              } else {
                  getWindow().setBackgroundDrawable(null);
                  background = background.mutate();
                  background.setAlpha(0);
              }
              getWindow().setBackgroundDrawable(background);
          } else {
              mActivity = null; // all done with it now.
          }
      }
  
      @Override
      protected Transition getViewsTransition() {
          Window window = getWindow();
          if (window == null) {
              return null;
          }
          if (mIsReturning) {
              return window.getReenterTransition();
          } else {
              return window.getEnterTransition();
          }
      }
  
      protected Transition getSharedElementTransition() {
          Window window = getWindow();
          if (window == null) {
              return null;
          }
          if (mIsReturning) {
              return window.getSharedElementReenterTransition();
          } else {
              return window.getSharedElementEnterTransition();
          }
      }
  
      private void startSharedElementTransition(Bundle sharedElementState) {
          ViewGroup decorView = getDecor();
          if (decorView == null) {
              return;
          }
          // Remove rejected shared elements
          ArrayList<String> rejectedNames = new ArrayList<String>(mAllSharedElementNames);
          rejectedNames.removeAll(mSharedElementNames);
          ArrayList<View> rejectedSnapshots = createSnapshots(sharedElementState, rejectedNames);
          if (mListener != null) {
              mListener.onRejectSharedElements(rejectedSnapshots);
          }
          removeNullViews(rejectedSnapshots);
          startRejectedAnimations(rejectedSnapshots);
  
          // Now start shared element transition
          ArrayList<View> sharedElementSnapshots = createSnapshots(sharedElementState,
                  mSharedElementNames);
          showViews(mSharedElements, true);
          scheduleSetSharedElementEnd(sharedElementSnapshots);
          ArrayList<SharedElementOriginalState> originalImageViewState =
                  setSharedElementState(sharedElementState, sharedElementSnapshots);
          requestLayoutForSharedElements();
  
          boolean startEnterTransition = allowOverlappingTransitions() && !mIsReturning;
          boolean startSharedElementTransition = true;
          setGhostVisibility(View.INVISIBLE);
          scheduleGhostVisibilityChange(View.INVISIBLE);
          pauseInput();
          Transition transition = beginTransition(decorView, startEnterTransition,
                  startSharedElementTransition);
          scheduleGhostVisibilityChange(View.VISIBLE);
          setGhostVisibility(View.VISIBLE);
  
          if (startEnterTransition) {
              startEnterTransition(transition);
          }
  
          setOriginalSharedElementState(mSharedElements, originalImageViewState);
  
          if (mResultReceiver != null) {
              // We can't trust that the view will disappear on the same frame that the shared
              // element appears here. Assure that we get at least 2 frames for double-buffering.
              decorView.postOnAnimation(new Runnable() {
                  int mAnimations;
  
                  @Override
                  public void run() {
                      if (mAnimations++ < MIN_ANIMATION_FRAMES) {
                          View decorView = getDecor();
                          if (decorView != null) {
                              decorView.postOnAnimation(this);
                          }
                      } else if (mResultReceiver != null) {
                          mResultReceiver.send(MSG_HIDE_SHARED_ELEMENTS, null);
                          mResultReceiver = null; // all done sending messages.
                      }
                  }
              });
          }
      }
  
      private static void removeNullViews(ArrayList<View> views) {
          if (views != null) {
              for (int i = views.size() - 1; i >= 0; i--) {
                  if (views.get(i) == null) {
                      views.remove(i);
                  }
              }
          }
      }
  
      private void onTakeSharedElements() {
          if (!mIsReadyForTransition || mSharedElementsBundle == null) {
              return;
          }
          final Bundle sharedElementState = mSharedElementsBundle;
          mSharedElementsBundle = null;
          OnSharedElementsReadyListener listener = new OnSharedElementsReadyListener() {
              @Override
              public void onSharedElementsReady() {
                  final View decorView = getDecor();
                  if (decorView != null) {
                      OneShotPreDrawListener.add(decorView, false, () -> {
                          startTransition(() -> {
                                  startSharedElementTransition(sharedElementState);
                          });
                      });
                      decorView.invalidate();
                  }
              }
          };
          if (mListener == null) {
              listener.onSharedElementsReady();
          } else {
              mListener.onSharedElementsArrived(mSharedElementNames, mSharedElements, listener);
          }
      }
  
      private void requestLayoutForSharedElements() {
          int numSharedElements = mSharedElements.size();
          for (int i = 0; i < numSharedElements; i++) {
              mSharedElements.get(i).requestLayout();
          }
      }
  
      private Transition beginTransition(ViewGroup decorView, boolean startEnterTransition,
              boolean startSharedElementTransition) {
          Transition sharedElementTransition = null;
          if (startSharedElementTransition) {
              if (!mSharedElementNames.isEmpty()) {
                  sharedElementTransition = configureTransition(getSharedElementTransition(), false);
              }
              if (sharedElementTransition == null) {
                  sharedElementTransitionStarted();
                  sharedElementTransitionComplete();
              } else {
                  sharedElementTransition.addListener(new TransitionListenerAdapter() {
                      @Override
                      public void onTransitionStart(Transition transition) {
                          sharedElementTransitionStarted();
                      }
  
                      @Override
                      public void onTransitionEnd(Transition transition) {
                          transition.removeListener(this);
                          sharedElementTransitionComplete();
                      }
                  });
              }
          }
          Transition viewsTransition = null;
          if (startEnterTransition) {
              mIsViewsTransitionStarted = true;
              if (mTransitioningViews != null && !mTransitioningViews.isEmpty()) {
                  viewsTransition = configureTransition(getViewsTransition(), true);
              }
              if (viewsTransition == null) {
                  viewsTransitionComplete();
              } else {
                  final ArrayList<View> transitioningViews = mTransitioningViews;
                  viewsTransition.addListener(new ContinueTransitionListener() {
                      @Override
                      public void onTransitionStart(Transition transition) {
                          mEnterViewsTransition = transition;
                          if (transitioningViews != null) {
                              showViews(transitioningViews, false);
                          }
                          super.onTransitionStart(transition);
                      }
  
                      @Override
                      public void onTransitionEnd(Transition transition) {
                          mEnterViewsTransition = null;
                          transition.removeListener(this);
                          viewsTransitionComplete();
                          super.onTransitionEnd(transition);
                      }
                  });
              }
          }
  
          Transition transition = mergeTransitions(sharedElementTransition, viewsTransition);
          if (transition != null) {
              transition.addListener(new ContinueTransitionListener());
              if (startEnterTransition) {
                  setTransitioningViewsVisiblity(View.INVISIBLE, false);
              }
              TransitionManager.beginDelayedTransition(decorView, transition);
              if (startEnterTransition) {
                  setTransitioningViewsVisiblity(View.VISIBLE, false);
              }
              decorView.invalidate();
          } else {
              transitionStarted();
          }
          return transition;
      }
  
      @Override
      protected void onTransitionsComplete() {
          moveSharedElementsFromOverlay();
          final ViewGroup decorView = getDecor();
          if (decorView != null) {
              decorView.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
  
              Window window = getWindow();
              if (window != null && mReplacedBackground == decorView.getBackground()) {
                  window.setBackgroundDrawable(null);
              }
          }
      }
  
      private void sharedElementTransitionStarted() {
          mSharedElementTransitionStarted = true;
          if (mIsExitTransitionComplete) {
              send(MSG_EXIT_TRANSITION_COMPLETE, null);
          }
      }
  
      private void startEnterTransition(Transition transition) {
          ViewGroup decorView = getDecor();
          if (!mIsReturning && decorView != null) {
              Drawable background = decorView.getBackground();
              if (background != null) {
                  background = background.mutate();
                  getWindow().setBackgroundDrawable(background);
                  mBackgroundAnimator = ObjectAnimator.ofInt(background, "alpha", 255);
                  mBackgroundAnimator.setDuration(getFadeDuration());
                  mBackgroundAnimator.addListener(new AnimatorListenerAdapter() {
                      @Override
                      public void onAnimationEnd(Animator animation) {
                          makeOpaque();
                          backgroundAnimatorComplete();
                      }
                  });
                  mBackgroundAnimator.start();
              } else if (transition != null) {
                  transition.addListener(new TransitionListenerAdapter() {
                      @Override
                      public void onTransitionEnd(Transition transition) {
                          transition.removeListener(this);
                          makeOpaque();
                      }
                  });
                  backgroundAnimatorComplete();
              } else {
                  makeOpaque();
                  backgroundAnimatorComplete();
              }
          } else {
              backgroundAnimatorComplete();
          }
      }
  
      public void stop() {
          // Restore the background to its previous state since the
          // Activity is stopping.
          if (mBackgroundAnimator != null) {
              mBackgroundAnimator.end();
              mBackgroundAnimator = null;
          } else if (mWasOpaque) {
              ViewGroup decorView = getDecor();
              if (decorView != null) {
                  Drawable drawable = decorView.getBackground();
                  if (drawable != null) {
                      drawable.setAlpha(1);
                  }
              }
          }
          makeOpaque();
          mIsCanceled = true;
          mResultReceiver = null;
          mActivity = null;
          moveSharedElementsFromOverlay();
          if (mTransitioningViews != null) {
              showViews(mTransitioningViews, true);
              setTransitioningViewsVisiblity(View.VISIBLE, true);
          }
          showViews(mSharedElements, true);
          clearState();
      }
  
      /**
       * Cancels the enter transition.
       * @return True if the enter transition is still pending capturing the target state. If so,
       * any transition started on the decor will do nothing.
       */
      public boolean cancelEnter() {
          setGhostVisibility(View.INVISIBLE);
          mHasStopped = true;
          mIsCanceled = true;
          clearState();
          return super.cancelPendingTransitions();
      }
  
      @Override
      protected void clearState() {
          mSharedElementsBundle = null;
          mEnterViewsTransition = null;
          mResultReceiver = null;
          if (mBackgroundAnimator != null) {
              mBackgroundAnimator.cancel();
              mBackgroundAnimator = null;
          }
          super.clearState();
      }
  
      private void makeOpaque() {
          if (!mHasStopped && mActivity != null) {
              if (mWasOpaque) {
                  mActivity.convertFromTranslucent();
              }
              mActivity = null;
          }
      }
  
      private boolean allowOverlappingTransitions() {
          return mIsReturning ? getWindow().getAllowReturnTransitionOverlap()
                  : getWindow().getAllowEnterTransitionOverlap();
      }
  
      private void startRejectedAnimations(final ArrayList<View> rejectedSnapshots) {
          if (rejectedSnapshots == null || rejectedSnapshots.isEmpty()) {
              return;
          }
          final ViewGroup decorView = getDecor();
          if (decorView != null) {
              ViewGroupOverlay overlay = decorView.getOverlay();
              ObjectAnimator animator = null;
              int numRejected = rejectedSnapshots.size();
              for (int i = 0; i < numRejected; i++) {
                  View snapshot = rejectedSnapshots.get(i);
                  overlay.add(snapshot);
                  animator = ObjectAnimator.ofFloat(snapshot, View.ALPHA, 1, 0);
                  animator.start();
              }
              animator.addListener(new AnimatorListenerAdapter() {
                  @Override
                  public void onAnimationEnd(Animator animation) {
                      ViewGroupOverlay overlay = decorView.getOverlay();
                      int numRejected = rejectedSnapshots.size();
                      for (int i = 0; i < numRejected; i++) {
                          overlay.remove(rejectedSnapshots.get(i));
                      }
                  }
              });
          }
      }
  
      protected void onRemoteExitTransitionComplete() {
          if (!allowOverlappingTransitions()) {
              startEnterTransitionOnly();
          }
      }
  
      private void startEnterTransitionOnly() {
          startTransition(new Runnable() {
              @Override
              public void run() {
                  boolean startEnterTransition = true;
                  boolean startSharedElementTransition = false;
                  ViewGroup decorView = getDecor();
                  if (decorView != null) {
                      Transition transition = beginTransition(decorView, startEnterTransition,
                              startSharedElementTransition);
                      startEnterTransition(transition);
                  }
              }
          });
      }
  }
  