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.content.Intent;
  import android.graphics.Color;
  import android.graphics.Matrix;
  import android.graphics.RectF;
  import android.graphics.drawable.ColorDrawable;
  import android.graphics.drawable.Drawable;
  import android.os.Build.VERSION_CODES;
  import android.os.Bundle;
  import android.os.Handler;
  import android.os.Message;
  import android.os.ResultReceiver;
  import android.transition.Transition;
  import android.transition.TransitionListenerAdapter;
  import android.transition.TransitionManager;
  import android.view.View;
  import android.view.ViewGroup;
  import android.view.Window;
  
  import com.android.internal.view.OneShotPreDrawListener;
  
  import java.util.ArrayList;
  
  /**
   * This ActivityTransitionCoordinator is created in ActivityOptions#makeSceneTransitionAnimation
   * to govern the exit of the Scene and the shared elements when calling an Activity as well as
   * the reentry of the Scene when coming back from the called Activity.
   */
  class ExitTransitionCoordinator extends ActivityTransitionCoordinator {
      private static final String TAG = "ExitTransitionCoordinator";
      private static final long MAX_WAIT_MS = 1000;
  
      private Bundle mSharedElementBundle;
      private boolean mExitNotified;
      private boolean mSharedElementNotified;
      private Activity mActivity;
      private boolean mIsBackgroundReady;
      private boolean mIsCanceled;
      private Handler mHandler;
      private ObjectAnimator mBackgroundAnimator;
      private boolean mIsHidden;
      private Bundle mExitSharedElementBundle;
      private boolean mIsExitStarted;
      private boolean mSharedElementsHidden;
      private HideSharedElementsCallback mHideSharedElementsCallback;
  
      public ExitTransitionCoordinator(Activity activity, Window window,
              SharedElementCallback listener, ArrayList<String> names,
              ArrayList<String> accepted, ArrayList<View> mapped, boolean isReturning) {
          super(window, names, listener, isReturning);
          viewsReady(mapSharedElements(accepted, mapped));
          stripOffscreenViews();
          mIsBackgroundReady = !isReturning;
          mActivity = activity;
      }
  
      void setHideSharedElementsCallback(HideSharedElementsCallback callback) {
          mHideSharedElementsCallback = callback;
      }
  
      @Override
      protected void onReceiveResult(int resultCode, Bundle resultData) {
          switch (resultCode) {
              case MSG_SET_REMOTE_RECEIVER:
                  stopCancel();
                  mResultReceiver = resultData.getParcelable(KEY_REMOTE_RECEIVER);
                  if (mIsCanceled) {
                      mResultReceiver.send(MSG_CANCEL, null);
                      mResultReceiver = null;
                  } else {
                      notifyComplete();
                  }
                  break;
              case MSG_HIDE_SHARED_ELEMENTS:
                  stopCancel();
                  if (!mIsCanceled) {
                      hideSharedElements();
                  }
                  break;
              case MSG_START_EXIT_TRANSITION:
                  mHandler.removeMessages(MSG_CANCEL);
                  startExit();
                  break;
              case MSG_SHARED_ELEMENT_DESTINATION:
                  mExitSharedElementBundle = resultData;
                  sharedElementExitBack();
                  break;
              case MSG_CANCEL:
                  mIsCanceled = true;
                  finish();
                  break;
          }
      }
  
      private void stopCancel() {
          if (mHandler != null) {
              mHandler.removeMessages(MSG_CANCEL);
          }
      }
  
      private void delayCancel() {
          if (mHandler != null) {
              mHandler.sendEmptyMessageDelayed(MSG_CANCEL, MAX_WAIT_MS);
          }
      }
  
      public void resetViews() {
          ViewGroup decorView = getDecor();
          if (decorView != null) {
              TransitionManager.endTransitions(decorView);
          }
          if (mTransitioningViews != null) {
              showViews(mTransitioningViews, true);
              setTransitioningViewsVisiblity(View.VISIBLE, true);
          }
          showViews(mSharedElements, true);
          mIsHidden = true;
          if (!mIsReturning && decorView != null) {
              decorView.suppressLayout(false);
          }
          moveSharedElementsFromOverlay();
          clearState();
      }
  
      private void sharedElementExitBack() {
          final ViewGroup decorView = getDecor();
          if (decorView != null) {
              decorView.suppressLayout(true);
          }
          if (decorView != null && mExitSharedElementBundle != null &&
                  !mExitSharedElementBundle.isEmpty() &&
                  !mSharedElements.isEmpty() && getSharedElementTransition() != null) {
              startTransition(new Runnable() {
                  public void run() {
                      startSharedElementExit(decorView);
                  }
              });
          } else {
              sharedElementTransitionComplete();
          }
      }
  
      private void startSharedElementExit(final ViewGroup decorView) {
          Transition transition = getSharedElementExitTransition();
          transition.addListener(new TransitionListenerAdapter() {
              @Override
              public void onTransitionEnd(Transition transition) {
                  transition.removeListener(this);
                  if (isViewsTransitionComplete()) {
                      delayCancel();
                  }
              }
          });
          final ArrayList<View> sharedElementSnapshots = createSnapshots(mExitSharedElementBundle,
                  mSharedElementNames);
          OneShotPreDrawListener.add(decorView, () -> {
              setSharedElementState(mExitSharedElementBundle, sharedElementSnapshots);
          });
          setGhostVisibility(View.INVISIBLE);
          scheduleGhostVisibilityChange(View.INVISIBLE);
          if (mListener != null) {
              mListener.onSharedElementEnd(mSharedElementNames, mSharedElements,
                      sharedElementSnapshots);
          }
          TransitionManager.beginDelayedTransition(decorView, transition);
          scheduleGhostVisibilityChange(View.VISIBLE);
          setGhostVisibility(View.VISIBLE);
          decorView.invalidate();
      }
  
      private void hideSharedElements() {
          moveSharedElementsFromOverlay();
          if (mHideSharedElementsCallback != null) {
              mHideSharedElementsCallback.hideSharedElements();
          }
          if (!mIsHidden) {
              hideViews(mSharedElements);
          }
          mSharedElementsHidden = true;
          finishIfNecessary();
      }
  
      public void startExit() {
          if (!mIsExitStarted) {
              backgroundAnimatorComplete();
              mIsExitStarted = true;
              pauseInput();
              ViewGroup decorView = getDecor();
              if (decorView != null) {
                  decorView.suppressLayout(true);
              }
              moveSharedElementsToOverlay();
              startTransition(new Runnable() {
                  @Override
                  public void run() {
                      if (mActivity != null) {
                          beginTransitions();
                      } else {
                          startExitTransition();
                      }
                  }
              });
          }
      }
  
      public void startExit(int resultCode, Intent data) {
          if (!mIsExitStarted) {
              mIsExitStarted = true;
              pauseInput();
              ViewGroup decorView = getDecor();
              if (decorView != null) {
                  decorView.suppressLayout(true);
              }
              mHandler = new Handler() {
                  @Override
                  public void handleMessage(Message msg) {
                      mIsCanceled = true;
                      finish();
                  }
              };
              delayCancel();
              moveSharedElementsToOverlay();
              if (decorView != null && decorView.getBackground() == null) {
                  getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
              }
              final boolean targetsM = decorView == null || decorView.getContext()
                      .getApplicationInfo().targetSdkVersion >= VERSION_CODES.M;
              ArrayList<String> sharedElementNames = targetsM ? mSharedElementNames :
                      mAllSharedElementNames;
              ActivityOptions options = ActivityOptions.makeSceneTransitionAnimation(mActivity, this,
                      sharedElementNames, resultCode, data);
              mActivity.convertToTranslucent(new Activity.TranslucentConversionListener() {
                  @Override
                  public void onTranslucentConversionComplete(boolean drawComplete) {
                      if (!mIsCanceled) {
                          fadeOutBackground();
                      }
                  }
              }, options);
              startTransition(new Runnable() {
                  @Override
                  public void run() {
                      startExitTransition();
                  }
              });
          }
      }
  
      public void stop() {
          if (mIsReturning && mActivity != null) {
              // Override the previous ActivityOptions. We don't want the
              // activity to have options since we're essentially canceling the
              // transition and finishing right now.
              mActivity.convertToTranslucent(null, null);
              finish();
          }
      }
  
      private void startExitTransition() {
          Transition transition = getExitTransition();
          ViewGroup decorView = getDecor();
          if (transition != null && decorView != null && mTransitioningViews != null) {
              setTransitioningViewsVisiblity(View.VISIBLE, false);
              TransitionManager.beginDelayedTransition(decorView, transition);
              setTransitioningViewsVisiblity(View.INVISIBLE, false);
              decorView.invalidate();
          } else {
              transitionStarted();
          }
      }
  
      private void fadeOutBackground() {
          if (mBackgroundAnimator == null) {
              ViewGroup decor = getDecor();
              Drawable background;
              if (decor != null && (background = decor.getBackground()) != null) {
                  background = background.mutate();
                  getWindow().setBackgroundDrawable(background);
                  mBackgroundAnimator = ObjectAnimator.ofInt(background, "alpha", 0);
                  mBackgroundAnimator.addListener(new AnimatorListenerAdapter() {
                      @Override
                      public void onAnimationEnd(Animator animation) {
                          mBackgroundAnimator = null;
                          if (!mIsCanceled) {
                              mIsBackgroundReady = true;
                              notifyComplete();
                          }
                          backgroundAnimatorComplete();
                      }
                  });
                  mBackgroundAnimator.setDuration(getFadeDuration());
                  mBackgroundAnimator.start();
              } else {
                  backgroundAnimatorComplete();
                  mIsBackgroundReady = true;
              }
          }
      }
  
      private Transition getExitTransition() {
          Transition viewsTransition = null;
          if (mTransitioningViews != null && !mTransitioningViews.isEmpty()) {
              viewsTransition = configureTransition(getViewsTransition(), true);
              removeExcludedViews(viewsTransition, mTransitioningViews);
              if (mTransitioningViews.isEmpty()) {
                  viewsTransition = null;
              }
          }
          if (viewsTransition == null) {
              viewsTransitionComplete();
          } else {
              final ArrayList<View> transitioningViews = mTransitioningViews;
              viewsTransition.addListener(new ContinueTransitionListener() {
                  @Override
                  public void onTransitionEnd(Transition transition) {
                      viewsTransitionComplete();
                      if (mIsHidden && transitioningViews != null) {
                          showViews(transitioningViews, true);
                          setTransitioningViewsVisiblity(View.VISIBLE, true);
                      }
                      if (mSharedElementBundle != null) {
                          delayCancel();
                      }
                      super.onTransitionEnd(transition);
                  }
              });
          }
          return viewsTransition;
      }
  
      private Transition getSharedElementExitTransition() {
          Transition sharedElementTransition = null;
          if (!mSharedElements.isEmpty()) {
              sharedElementTransition = configureTransition(getSharedElementTransition(), false);
          }
          if (sharedElementTransition == null) {
              sharedElementTransitionComplete();
          } else {
              sharedElementTransition.addListener(new ContinueTransitionListener() {
                  @Override
                  public void onTransitionEnd(Transition transition) {
                      sharedElementTransitionComplete();
                      if (mIsHidden) {
                          showViews(mSharedElements, true);
                      }
                      super.onTransitionEnd(transition);
                  }
              });
              mSharedElements.get(0).invalidate();
          }
          return sharedElementTransition;
      }
  
      private void beginTransitions() {
          Transition sharedElementTransition = getSharedElementExitTransition();
          Transition viewsTransition = getExitTransition();
  
          Transition transition = mergeTransitions(sharedElementTransition, viewsTransition);
          ViewGroup decorView = getDecor();
          if (transition != null && decorView != null) {
              setGhostVisibility(View.INVISIBLE);
              scheduleGhostVisibilityChange(View.INVISIBLE);
              if (viewsTransition != null) {
                  setTransitioningViewsVisiblity(View.VISIBLE, false);
              }
              TransitionManager.beginDelayedTransition(decorView, transition);
              scheduleGhostVisibilityChange(View.VISIBLE);
              setGhostVisibility(View.VISIBLE);
              if (viewsTransition != null) {
                  setTransitioningViewsVisiblity(View.INVISIBLE, false);
              }
              decorView.invalidate();
          } else {
              transitionStarted();
          }
      }
  
      protected boolean isReadyToNotify() {
          return mSharedElementBundle != null && mResultReceiver != null && mIsBackgroundReady;
      }
  
      @Override
      protected void sharedElementTransitionComplete() {
          mSharedElementBundle = mExitSharedElementBundle == null
                  ? captureSharedElementState() : captureExitSharedElementsState();
          super.sharedElementTransitionComplete();
      }
  
      private Bundle captureExitSharedElementsState() {
          Bundle bundle = new Bundle();
          RectF bounds = new RectF();
          Matrix matrix = new Matrix();
          for (int i = 0; i < mSharedElements.size(); i++) {
              String name = mSharedElementNames.get(i);
              Bundle sharedElementState = mExitSharedElementBundle.getBundle(name);
              if (sharedElementState != null) {
                  bundle.putBundle(name, sharedElementState);
              } else {
                  View view = mSharedElements.get(i);
                  captureSharedElementState(view, name, bundle, matrix, bounds);
              }
          }
          return bundle;
      }
  
      @Override
      protected void onTransitionsComplete() {
          notifyComplete();
      }
  
      protected void notifyComplete() {
          if (isReadyToNotify()) {
              if (!mSharedElementNotified) {
                  mSharedElementNotified = true;
                  delayCancel();
  
                  if (!mActivity.isTopOfTask()) {
                      mResultReceiver.send(MSG_ALLOW_RETURN_TRANSITION, null);
                  }
  
                  if (mListener == null) {
                      mResultReceiver.send(MSG_TAKE_SHARED_ELEMENTS, mSharedElementBundle);
                      notifyExitComplete();
                  } else {
                      final ResultReceiver resultReceiver = mResultReceiver;
                      final Bundle sharedElementBundle = mSharedElementBundle;
                      mListener.onSharedElementsArrived(mSharedElementNames, mSharedElements,
                              new OnSharedElementsReadyListener() {
                                  @Override
                                  public void onSharedElementsReady() {
                                      resultReceiver.send(MSG_TAKE_SHARED_ELEMENTS,
                                              sharedElementBundle);
                                      notifyExitComplete();
                                  }
                              });
                  }
              } else {
                  notifyExitComplete();
              }
          }
      }
  
      private void notifyExitComplete() {
          if (!mExitNotified && isViewsTransitionComplete()) {
              mExitNotified = true;
              mResultReceiver.send(MSG_EXIT_TRANSITION_COMPLETE, null);
              mResultReceiver = null; // done talking
              ViewGroup decorView = getDecor();
              if (!mIsReturning && decorView != null) {
                  decorView.suppressLayout(false);
              }
              finishIfNecessary();
          }
      }
  
      private void finishIfNecessary() {
          if (mIsReturning && mExitNotified && mActivity != null && (mSharedElements.isEmpty() ||
                  mSharedElementsHidden)) {
              finish();
          }
          if (!mIsReturning && mExitNotified) {
              mActivity = null; // don't need it anymore
          }
      }
  
      private void finish() {
          stopCancel();
          if (mActivity != null) {
              mActivity.mActivityTransitionState.clear();
              mActivity.finish();
              mActivity.overridePendingTransition(0, 0);
              mActivity = null;
          }
          // Clear the state so that we can't hold any references accidentally and leak memory.
          clearState();
      }
  
      @Override
      protected void clearState() {
          mHandler = null;
          mSharedElementBundle = null;
          if (mBackgroundAnimator != null) {
              mBackgroundAnimator.cancel();
              mBackgroundAnimator = null;
          }
          mExitSharedElementBundle = null;
          super.clearState();
      }
  
      @Override
      protected boolean moveSharedElementWithParent() {
          return !mIsReturning;
      }
  
      @Override
      protected Transition getViewsTransition() {
          if (mIsReturning) {
              return getWindow().getReturnTransition();
          } else {
              return getWindow().getExitTransition();
          }
      }
  
      protected Transition getSharedElementTransition() {
          if (mIsReturning) {
              return getWindow().getSharedElementReturnTransition();
          } else {
              return getWindow().getSharedElementExitTransition();
          }
      }
  
      interface HideSharedElementsCallback {
          void hideSharedElements();
      }
  }
  