1 /* 2 * Copyright (C) 2020 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.google.android.setupdesign.transition; 18 19 import android.annotation.TargetApi; 20 import android.app.Activity; 21 import android.app.ActivityOptions; 22 import android.app.Fragment; 23 import android.content.ActivityNotFoundException; 24 import android.content.Context; 25 import android.content.Intent; 26 import android.content.res.TypedArray; 27 import android.os.Build; 28 import android.os.Build.VERSION_CODES; 29 import android.os.Bundle; 30 import android.os.Parcelable; 31 import android.util.Log; 32 import android.view.Window; 33 import androidx.annotation.IntDef; 34 import androidx.annotation.Nullable; 35 import androidx.annotation.VisibleForTesting; 36 import com.google.android.material.transition.platform.MaterialSharedAxis; 37 import com.google.android.setupcompat.partnerconfig.PartnerConfig; 38 import com.google.android.setupcompat.partnerconfig.PartnerConfigHelper; 39 import com.google.android.setupcompat.util.BuildCompatUtils; 40 import com.google.android.setupdesign.R; 41 import com.google.android.setupdesign.util.ThemeHelper; 42 import java.lang.annotation.Retention; 43 import java.lang.annotation.RetentionPolicy; 44 45 /** Helper class for apply the transition to the pages which uses platform version. */ 46 public class TransitionHelper { 47 48 private static final String TAG = "TransitionHelper"; 49 50 /* 51 * In Setup Wizard, all Just-a-sec style screens (i.e. screens that has an indeterminate 52 * progress bar and automatically finishes itself), should do a cross-fade when entering or 53 * exiting the screen. For all other screens, the transition should be a slide-in-from-right 54 * or customized. 55 * 56 * We use two different ways to override the transitions. The first is calling 57 * overridePendingTransition in code, and the second is using windowAnimationStyle in the theme. 58 * They have the following priority when framework is figuring out what transition to use: 59 * 1. overridePendingTransition, entering activity (highest priority) 60 * 2. overridePendingTransition, exiting activity 61 * 3. windowAnimationStyle, entering activity 62 * 4. windowAnimationStyle, exiting activity 63 * 64 * This is why, in general, overridePendingTransition is used to specify the fade animation, 65 * while windowAnimationStyle is used to specify the slide transition. This way fade animation 66 * will take priority over the slide animation. 67 * 68 * Below are types of animation when switching activities. These are return values for 69 * {@link #getTransition()}. Each of these values represents 4 animations: (backward exit, 70 * backward enter, forward exit, forward enter). 71 * 72 * We override the transition in the following flow 73 * +--------------+-------------------------+--------------------------+ 74 * | | going forward | going backward | 75 * +--------------+-------------------------+--------------------------+ 76 * | old activity | startActivity(OnResult) | onActivityResult | 77 * +--------------+-------------------------+--------------------------+ 78 * | new activity | onStart | finish (RESULT_CANCELED) | 79 * +--------------+-------------------------+--------------------------+ 80 */ 81 82 /** The constant of transition type. */ 83 @Retention(RetentionPolicy.SOURCE) 84 @IntDef({ 85 TRANSITION_NONE, 86 TRANSITION_NO_OVERRIDE, 87 TRANSITION_FRAMEWORK_DEFAULT, 88 TRANSITION_SLIDE, 89 TRANSITION_FADE, 90 TRANSITION_FRAMEWORK_DEFAULT_PRE_P, 91 TRANSITION_CAPTIVE, 92 }) 93 public @interface TransitionType {} 94 95 /** No transition, as in overridePendingTransition(0, 0). */ 96 public static final int TRANSITION_NONE = -1; 97 98 /** 99 * No override. If this is specified as the transition, overridePendingTransition will not be 100 * called. 101 */ 102 public static final int TRANSITION_NO_OVERRIDE = 0; 103 104 /** 105 * Override the transition to the framework default. This values are read from {@link 106 * android.R.style#Animation_Activity}. 107 */ 108 public static final int TRANSITION_FRAMEWORK_DEFAULT = 1; 109 110 /** Override the transition to a slide-in-from-right (or from-left for RTL locales). */ 111 public static final int TRANSITION_SLIDE = 2; 112 113 /** 114 * Override the transition to fade in the new activity, while keeping the old activity. Setup 115 * wizard does not use cross fade to avoid the bright-dim-bright effect when transitioning between 116 * two screens that look similar. 117 */ 118 public static final int TRANSITION_FADE = 3; 119 120 /** Override the transition to the old framework default pre P. */ 121 public static final int TRANSITION_FRAMEWORK_DEFAULT_PRE_P = 4; 122 123 /** 124 * Override the transition to the specific transition and the transition type will depends on the 125 * partner resource. 126 */ 127 // TODO: Add new partner resource to determine which transition type would be apply. 128 public static final int TRANSITION_CAPTIVE = 5; 129 130 /** 131 * No override. If this is specified as the transition, the enter/exit transition of the window 132 * will not be set and keep original behavior. 133 */ 134 public static final int CONFIG_TRANSITION_NONE = 0; 135 136 /** Override the transition to the specific type that will depend on the partner resource. */ 137 public static final int CONFIG_TRANSITION_SHARED_X_AXIS = 1; 138 139 /** 140 * Passed in an intent as EXTRA_ACTIVITY_OPTIONS. This is the {@link ActivityOptions} of the 141 * transition used in {@link Activity#startActivity} or {@link Activity#startActivityForResult}. 142 */ 143 public static final String EXTRA_ACTIVITY_OPTIONS = "sud:activity_options"; 144 145 /** A flag to avoid the {@link Activity#finish} been called more than once. */ 146 @VisibleForTesting static boolean isFinishCalled = false; 147 148 /** A flag to avoid the {@link Activity#startActivity} called more than once. */ 149 @VisibleForTesting static boolean isStartActivity = false; 150 151 /** A flag to avoid the {@link Activity#startActivityForResult} called more than once. */ 152 @VisibleForTesting static boolean isStartActivityForResult = false; 153 TransitionHelper()154 private TransitionHelper() {} 155 156 /** 157 * Apply the transition for going forward which is decided by partner resource {@link 158 * PartnerConfig#CONFIG_TRANSITION_TYPE} and system property {@code setupwizard.transition_type}. 159 * The default transition that will be applied is {@link #TRANSITION_SLIDE}. The timing to apply 160 * the transition is going forward from the previous activity to this, or going forward from this 161 * activity to the next. 162 * 163 * <p>For example, in the flow below, the forward transitions will be applied to all arrows 164 * pointing to the right. Previous screen --> This screen --> Next screen 165 */ 166 @TargetApi(VERSION_CODES.LOLLIPOP) applyForwardTransition(Activity activity)167 public static void applyForwardTransition(Activity activity) { 168 applyForwardTransition(activity, TRANSITION_CAPTIVE); 169 } 170 171 /** 172 * Apply the transition for going forward which is decided by partner resource {@link 173 * PartnerConfig#CONFIG_TRANSITION_TYPE} and system property {@code setupwizard.transition_type}. 174 * The default transition that will be applied is {@link #CONFIG_TRANSITION_NONE}. The timing to 175 * apply the transition is going forward from the previous {@link Fragment} to this, or going 176 * forward from this {@link Fragment} to the next. 177 */ 178 @TargetApi(VERSION_CODES.M) applyForwardTransition(Fragment fragment)179 public static void applyForwardTransition(Fragment fragment) { 180 if (Build.VERSION.SDK_INT >= VERSION_CODES.M) { 181 if (getConfigTransitionType(fragment.getContext()) == CONFIG_TRANSITION_SHARED_X_AXIS) { 182 MaterialSharedAxis exitTransition = 183 new MaterialSharedAxis(MaterialSharedAxis.X, /* forward= */ true); 184 fragment.setExitTransition(exitTransition); 185 186 MaterialSharedAxis enterTransition = 187 new MaterialSharedAxis(MaterialSharedAxis.X, /* forward= */ true); 188 fragment.setEnterTransition(enterTransition); 189 } else { 190 Log.w(TAG, "Not apply the forward transition for platform fragment."); 191 } 192 } else { 193 Log.w( 194 TAG, 195 "Not apply the forward transition for platform fragment. The API is supported from" 196 + " Android Sdk " 197 + VERSION_CODES.M); 198 } 199 } 200 201 /** 202 * Apply the transition for going forward. This is applied when going forward from the previous 203 * activity to this, or going forward from this activity to the next. 204 * 205 * <p>For example, in the flow below, the forward transitions will be applied to all arrows 206 * pointing to the right. Previous screen --> This screen --> Next screen 207 */ 208 @TargetApi(VERSION_CODES.LOLLIPOP) applyForwardTransition(Activity activity, @TransitionType int transitionId)209 public static void applyForwardTransition(Activity activity, @TransitionType int transitionId) { 210 if (transitionId == TRANSITION_SLIDE) { 211 activity.overridePendingTransition(R.anim.sud_slide_next_in, R.anim.sud_slide_next_out); 212 } else if (transitionId == TRANSITION_FADE) { 213 activity.overridePendingTransition(android.R.anim.fade_in, R.anim.sud_stay); 214 } else if (transitionId == TRANSITION_FRAMEWORK_DEFAULT) { 215 TypedArray typedArray = 216 activity.obtainStyledAttributes( 217 android.R.style.Animation_Activity, 218 new int[] { 219 android.R.attr.activityOpenEnterAnimation, android.R.attr.activityOpenExitAnimation 220 }); 221 activity.overridePendingTransition( 222 typedArray.getResourceId(/* index= */ 0, /* defValue= */ 0), 223 typedArray.getResourceId(/* index= */ 1, /* defValue= */ 0)); 224 typedArray.recycle(); 225 } else if (transitionId == TRANSITION_FRAMEWORK_DEFAULT_PRE_P) { 226 activity.overridePendingTransition( 227 R.anim.sud_pre_p_activity_open_enter, R.anim.sud_pre_p_activity_open_exit); 228 } else if (transitionId == TRANSITION_NONE) { 229 // For TRANSITION_NONE, turn off the transition 230 activity.overridePendingTransition(/* enterAnim= */ 0, /* exitAnim= */ 0); 231 } else if (transitionId == TRANSITION_CAPTIVE) { 232 if (Build.VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { 233 // 1. Do not change the transition behavior by default 234 // 2. If the flag present, apply the transition from transition type 235 if (getConfigTransitionType(activity) == CONFIG_TRANSITION_SHARED_X_AXIS) { 236 Window window = activity.getWindow(); 237 if (window != null) { 238 MaterialSharedAxis exitTransition = 239 new MaterialSharedAxis(MaterialSharedAxis.X, /* forward= */ true); 240 window.setExitTransition(exitTransition); 241 242 window.setAllowEnterTransitionOverlap(true); 243 244 MaterialSharedAxis enterTransition = 245 new MaterialSharedAxis(MaterialSharedAxis.X, /* forward= */ true); 246 window.setEnterTransition(enterTransition); 247 } else { 248 Log.w(TAG, "applyForwardTransition: Invalid window=" + window); 249 } 250 } 251 } else { 252 Log.w(TAG, "This API is supported from Android Sdk " + VERSION_CODES.LOLLIPOP); 253 } 254 } 255 // For TRANSITION_NO_OVERRIDE or other values, do not override the transition 256 } 257 258 /** 259 * Apply the transition for going backward which is decided by partner resource {@link 260 * PartnerConfig#CONFIG_TRANSITION_TYPE} and system property {@code setupwizard.transition_type}. 261 * The default transition that will be applied is {@link #TRANSITION_SLIDE}. The timing to apply 262 * the transition is going backward from the next activity to this, or going backward from this 263 * activity to the previous. 264 * 265 * <p>For example, in the flow below, the backward transitions will be applied to all arrows 266 * pointing to the left. Previous screen <-- This screen <-- Next screen 267 */ 268 @TargetApi(VERSION_CODES.LOLLIPOP) applyBackwardTransition(Activity activity)269 public static void applyBackwardTransition(Activity activity) { 270 applyBackwardTransition(activity, TRANSITION_CAPTIVE); 271 } 272 273 /** 274 * Apply the transition for going backward which is decided by partner resource {@link 275 * PartnerConfig#CONFIG_TRANSITION_TYPE} and system property {@code setupwizard.transition_type}. 276 * The default transition that will be applied is {@link #CONFIG_TRANSITION_NONE}. The timing to 277 * apply the transition is going backward from the next {@link Fragment} to this, or going 278 * backward from this {@link Fragment} to the previous. 279 */ 280 @TargetApi(VERSION_CODES.M) applyBackwardTransition(Fragment fragment)281 public static void applyBackwardTransition(Fragment fragment) { 282 if (Build.VERSION.SDK_INT >= VERSION_CODES.M) { 283 if (getConfigTransitionType(fragment.getContext()) == CONFIG_TRANSITION_SHARED_X_AXIS) { 284 MaterialSharedAxis returnTransition = 285 new MaterialSharedAxis(MaterialSharedAxis.X, /* forward= */ false); 286 fragment.setReturnTransition(returnTransition); 287 288 MaterialSharedAxis reenterTransition = 289 new MaterialSharedAxis(MaterialSharedAxis.X, /* forward= */ false); 290 fragment.setReenterTransition(reenterTransition); 291 } else { 292 Log.w(TAG, "Not apply the backward transition for platform fragment."); 293 } 294 } else { 295 Log.w( 296 TAG, 297 "Not apply the backward transition for platform fragment. The API is supported from" 298 + " Android Sdk " 299 + VERSION_CODES.M); 300 } 301 } 302 303 /** 304 * Apply the transition for going backward. This is applied when going backward from the next 305 * activity to this, or going backward from this activity to the previous. 306 * 307 * <p>For example, in the flow below, the backward transitions will be applied to all arrows 308 * pointing to the left. Previous screen <-- This screen <-- Next screen 309 */ 310 @TargetApi(VERSION_CODES.LOLLIPOP) applyBackwardTransition(Activity activity, @TransitionType int transitionId)311 public static void applyBackwardTransition(Activity activity, @TransitionType int transitionId) { 312 if (transitionId == TRANSITION_SLIDE) { 313 activity.overridePendingTransition(R.anim.sud_slide_back_in, R.anim.sud_slide_back_out); 314 } else if (transitionId == TRANSITION_FADE) { 315 activity.overridePendingTransition(R.anim.sud_stay, android.R.anim.fade_out); 316 } else if (transitionId == TRANSITION_FRAMEWORK_DEFAULT) { 317 TypedArray typedArray = 318 activity.obtainStyledAttributes( 319 android.R.style.Animation_Activity, 320 new int[] { 321 android.R.attr.activityCloseEnterAnimation, 322 android.R.attr.activityCloseExitAnimation 323 }); 324 activity.overridePendingTransition( 325 typedArray.getResourceId(/* index= */ 0, /* defValue= */ 0), 326 typedArray.getResourceId(/* index= */ 1, /* defValue= */ 0)); 327 typedArray.recycle(); 328 } else if (transitionId == TRANSITION_FRAMEWORK_DEFAULT_PRE_P) { 329 activity.overridePendingTransition( 330 R.anim.sud_pre_p_activity_close_enter, R.anim.sud_pre_p_activity_close_exit); 331 } else if (transitionId == TRANSITION_NONE) { 332 // For TRANSITION_NONE, turn off the transition 333 activity.overridePendingTransition(/* enterAnim= */ 0, /* exitAnim= */ 0); 334 } else if (transitionId == TRANSITION_CAPTIVE) { 335 if (Build.VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { 336 // 1. Do not change the transition behavior by default 337 // 2. If the flag present, apply the transition from transition type 338 if (getConfigTransitionType(activity) == CONFIG_TRANSITION_SHARED_X_AXIS) { 339 Window window = activity.getWindow(); 340 if (window != null) { 341 MaterialSharedAxis reenterTransition = 342 new MaterialSharedAxis(MaterialSharedAxis.X, /* forward= */ false); 343 window.setReenterTransition(reenterTransition); 344 345 MaterialSharedAxis returnTransition = 346 new MaterialSharedAxis(MaterialSharedAxis.X, /* forward= */ false); 347 window.setReturnTransition(returnTransition); 348 } else { 349 Log.w(TAG, "applyBackwardTransition: Invalid window=" + window); 350 } 351 } 352 } else { 353 Log.w(TAG, "This API is supported from Android Sdk " + VERSION_CODES.LOLLIPOP); 354 } 355 } 356 // For TRANSITION_NO_OVERRIDE or other values, do not override the transition 357 } 358 359 /** 360 * A wrapper method, create an {@link android.app.ActivityOptions} to transition between 361 * activities as the {@link ActivityOptions} parameter of {@link Activity#startActivity}. 362 * 363 * @throws IllegalArgumentException is thrown when {@code activity} or {@code intent} is null. 364 * @throws android.content.ActivityNotFoundException if there was no {@link Activity} found to run 365 * the given Intent. 366 */ startActivityWithTransition(Activity activity, Intent intent)367 public static void startActivityWithTransition(Activity activity, Intent intent) { 368 startActivityWithTransition(activity, intent, /* overrideActivityOptions= */ null); 369 } 370 371 /** 372 * A wrapper method, create an {@link android.app.ActivityOptions} to transition between 373 * activities as the {@link ActivityOptions} parameter of {@link Activity#startActivity}. 374 * 375 * @throws IllegalArgumentException is thrown when {@code activity} or {@code intent} is null. 376 * @throws android.content.ActivityNotFoundException if there was no {@link Activity} found to run 377 * the given Intent. 378 */ startActivityWithTransition( Activity activity, Intent intent, Bundle overrideActivityOptions)379 public static void startActivityWithTransition( 380 Activity activity, Intent intent, Bundle overrideActivityOptions) { 381 if (activity == null) { 382 throw new IllegalArgumentException("Invalid activity=" + activity); 383 } 384 385 if (intent == null) { 386 throw new IllegalArgumentException("Invalid intent=" + intent); 387 } 388 389 if ((intent.getFlags() & Intent.FLAG_ACTIVITY_NEW_TASK) == Intent.FLAG_ACTIVITY_NEW_TASK) { 390 Log.e( 391 TAG, 392 "The transition won't take effect since the WindowManager does not allow override new" 393 + " task transitions"); 394 } 395 396 if (!isStartActivity) { 397 isStartActivity = true; 398 if (getConfigTransitionType(activity) == CONFIG_TRANSITION_SHARED_X_AXIS) { 399 if (Build.VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { 400 if (activity.getWindow() != null 401 && !activity.getWindow().hasFeature(Window.FEATURE_ACTIVITY_TRANSITIONS)) { 402 Log.w( 403 TAG, 404 "The transition won't take effect due to NO FEATURE_ACTIVITY_TRANSITIONS feature"); 405 } 406 407 Bundle bundleActivityOptions; 408 if (overrideActivityOptions != null) { 409 bundleActivityOptions = overrideActivityOptions; 410 } else { 411 bundleActivityOptions = makeActivityOptions(activity, intent); 412 } 413 intent.putExtra(EXTRA_ACTIVITY_OPTIONS, (Parcelable) bundleActivityOptions); 414 activity.startActivity(intent, bundleActivityOptions); 415 } else { 416 Log.w( 417 TAG, 418 "Fallback to using startActivity due to the" 419 + " ActivityOptions#makeSceneTransitionAnimation is supported from Android Sdk " 420 + VERSION_CODES.LOLLIPOP); 421 startActivityWithTransitionInternal(activity, intent, overrideActivityOptions); 422 } 423 } else { 424 startActivityWithTransitionInternal(activity, intent, overrideActivityOptions); 425 } 426 } 427 isStartActivity = false; 428 } 429 startActivityWithTransitionInternal( Activity activity, Intent intent, Bundle overrideActivityOptions)430 private static void startActivityWithTransitionInternal( 431 Activity activity, Intent intent, Bundle overrideActivityOptions) { 432 try { 433 if (Build.VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN) { 434 if (getConfigTransitionType(activity) == CONFIG_TRANSITION_SHARED_X_AXIS 435 && overrideActivityOptions != null) { 436 activity.startActivity(intent, overrideActivityOptions); 437 } else { 438 activity.startActivity(intent); 439 } 440 } else { 441 Log.w( 442 TAG, 443 "Fallback to using startActivity(Intent) due to the startActivity(Intent, Bundle) is" 444 + " supported from Android Sdk " 445 + VERSION_CODES.JELLY_BEAN); 446 activity.startActivity(intent); 447 } 448 } catch (ActivityNotFoundException e) { 449 Log.w(TAG, "Activity not found when startActivity with transition."); 450 isStartActivity = false; 451 throw e; 452 } 453 } 454 startActivityForResultWithTransition( Activity activity, Intent intent, int requestCode)455 public static void startActivityForResultWithTransition( 456 Activity activity, Intent intent, int requestCode) { 457 startActivityForResultWithTransition( 458 activity, intent, requestCode, /* overrideActivityOptions= */ null); 459 } 460 461 /** 462 * A wrapper method, create an {@link android.app.ActivityOptions} to transition between 463 * activities as the {@code activityOptions} parameter of {@link Activity#startActivityForResult}. 464 * 465 * @throws IllegalArgumentException is thrown when {@code activity} or {@code intent} is null. 466 * @throws android.content.ActivityNotFoundException if there was no {@link Activity} found to run 467 * the given Intent. 468 */ startActivityForResultWithTransition( Activity activity, Intent intent, int requestCode, Bundle overrideActivityOptions)469 public static void startActivityForResultWithTransition( 470 Activity activity, Intent intent, int requestCode, Bundle overrideActivityOptions) { 471 if (activity == null) { 472 throw new IllegalArgumentException("Invalid activity=" + activity); 473 } 474 475 if (intent == null) { 476 throw new IllegalArgumentException("Invalid intent=" + intent); 477 } 478 479 if ((intent.getFlags() & Intent.FLAG_ACTIVITY_NEW_TASK) == Intent.FLAG_ACTIVITY_NEW_TASK) { 480 Log.e( 481 TAG, 482 "The transition won't take effect since the WindowManager does not allow override new" 483 + " task transitions"); 484 } 485 486 if (!isStartActivityForResult) { 487 isStartActivityForResult = true; 488 if (getConfigTransitionType(activity) == CONFIG_TRANSITION_SHARED_X_AXIS) { 489 if (Build.VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { 490 if (activity.getWindow() != null 491 && !activity.getWindow().hasFeature(Window.FEATURE_ACTIVITY_TRANSITIONS)) { 492 Log.w( 493 TAG, 494 "The transition won't take effect due to NO FEATURE_ACTIVITY_TRANSITIONS feature"); 495 } 496 497 Bundle bundleActivityOptions; 498 if (overrideActivityOptions != null) { 499 bundleActivityOptions = overrideActivityOptions; 500 } else { 501 bundleActivityOptions = makeActivityOptions(activity, intent); 502 } 503 intent.putExtra(EXTRA_ACTIVITY_OPTIONS, (Parcelable) bundleActivityOptions); 504 activity.startActivityForResult(intent, requestCode, bundleActivityOptions); 505 } else { 506 Log.w( 507 TAG, 508 "Fallback to using startActivityForResult API due to the" 509 + " ActivityOptions#makeSceneTransitionAnimation is supported from Android Sdk " 510 + VERSION_CODES.LOLLIPOP); 511 startActivityForResultWithTransitionInternal( 512 activity, intent, requestCode, overrideActivityOptions); 513 } 514 } else { 515 startActivityForResultWithTransitionInternal( 516 activity, intent, requestCode, overrideActivityOptions); 517 } 518 isStartActivityForResult = false; 519 } 520 } 521 startActivityForResultWithTransitionInternal( Activity activity, Intent intent, int requestCode, Bundle overrideActivityOptions)522 private static void startActivityForResultWithTransitionInternal( 523 Activity activity, Intent intent, int requestCode, Bundle overrideActivityOptions) { 524 try { 525 if (Build.VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN) { 526 if (getConfigTransitionType(activity) == CONFIG_TRANSITION_SHARED_X_AXIS 527 && overrideActivityOptions != null) { 528 activity.startActivityForResult(intent, requestCode, overrideActivityOptions); 529 } else { 530 activity.startActivityForResult(intent, requestCode); 531 } 532 } else { 533 Log.w( 534 TAG, 535 "Fallback to using startActivityForResult(Intent) due to the" 536 + " startActivityForResult(Intent,int) is supported from Android Sdk " 537 + VERSION_CODES.JELLY_BEAN); 538 activity.startActivityForResult(intent, requestCode); 539 } 540 } catch (ActivityNotFoundException e) { 541 Log.w(TAG, "Activity not found when startActivityForResult with transition."); 542 isStartActivityForResult = false; 543 throw e; 544 } 545 } 546 547 /** 548 * A wrapper method, calling {@link Activity#finishAfterTransition()} to trigger exit transition 549 * when running in Android S and the transition type {link #CONFIG_TRANSITION_SHARED_X_AXIS}. 550 * 551 * @throws IllegalArgumentException is thrown when {@code activity} is null. 552 */ finishActivity(Activity activity)553 public static void finishActivity(Activity activity) { 554 if (activity == null) { 555 throw new IllegalArgumentException("Invalid activity=" + activity); 556 } 557 558 // Avoids finish been called more than once. 559 if (!isFinishCalled) { 560 isFinishCalled = true; 561 if (Build.VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP 562 && getConfigTransitionType(activity) == CONFIG_TRANSITION_SHARED_X_AXIS) { 563 activity.finishAfterTransition(); 564 } else { 565 Log.w( 566 TAG, 567 "Fallback to using Activity#finish() due to the" 568 + " Activity#finishAfterTransition() is supported from Android Sdk " 569 + VERSION_CODES.LOLLIPOP); 570 activity.finish(); 571 } 572 } 573 isFinishCalled = false; 574 } 575 576 /** 577 * Returns the transition type from the {@link PartnerConfig#CONFIG_TRANSITION_TYPE} partner 578 * resource on Android S, otherwise returns {@link #CONFIG_TRANSITION_NONE}. 579 */ getConfigTransitionType(Context context)580 public static int getConfigTransitionType(Context context) { 581 return BuildCompatUtils.isAtLeastS() && ThemeHelper.shouldApplyExtendedPartnerConfig(context) 582 ? PartnerConfigHelper.get(context) 583 .getInteger(context, PartnerConfig.CONFIG_TRANSITION_TYPE, CONFIG_TRANSITION_NONE) 584 : CONFIG_TRANSITION_NONE; 585 } 586 587 /** 588 * A wrapper method, create a {@link Bundle} from {@link ActivityOptions} to transition between 589 * Activities using cross-Activity scene animations. This {@link Bundle} that can be used with 590 * {@link Context#startActivity(Intent, Bundle)} and related methods. 591 * 592 * <p>Example usage: 593 * 594 * <pre>{@code 595 * Intent intent = new Intent("com.example.NEXT_ACTIVITY"); 596 * activity.startActivity(intent, TransitionHelper.makeActivityOptions(activity, intent, null); 597 * }</pre> 598 * 599 * <p>Unexpected usage: 600 * 601 * <pre>{@code 602 * Intent intent = new Intent("com.example.NEXT_ACTIVITY"); 603 * Intent intent2 = new Intent("com.example.NEXT_ACTIVITY"); 604 * activity.startActivity(intent, TransitionHelper.makeActivityOptions(activity, intent2, null); 605 * }</pre> 606 */ 607 @Nullable makeActivityOptions(Activity activity, Intent intent)608 public static Bundle makeActivityOptions(Activity activity, Intent intent) { 609 return makeActivityOptions(activity, intent, false); 610 } 611 612 /** 613 * A wrapper method, create a {@link Bundle} from {@link ActivityOptions} to transition between 614 * Activities using cross-Activity scene animations. This {@link Bundle} that can be used with 615 * {@link Context#startActivity(Intent, Bundle)} and related methods. When this {@code activity} 616 * is a no UI activity(the activity doesn't inflate any layouts), you will need to pass the bundle 617 * coming from previous UI activity as the {@link ActivityOptions}, otherwise, the transition 618 * won't be take effect. The {@code overrideActivityOptionsFromIntent} is supporting this purpose 619 * to return the {@link ActivityOptions} instead of creating from this no UI activity while the 620 * transition is apply {@link #CONFIG_TRANSITION_SHARED_X_AXIS} config. Moreover, the 621 * startActivity*WithTransition relative methods and {@link #makeActivityOptions} will put {@link 622 * ActivityOptions} to the {@code intent} by default, you can get the {@link ActivityOptions} 623 * which makes from previous activity by accessing {@link #EXTRA_ACTIVITY_OPTIONS} extra from 624 * {@link Activity#getIntent()}. 625 * 626 * <p>Example usage of a no UI activity: 627 * 628 * <pre>{@code 629 * Intent intent = new Intent("com.example.NEXT_ACTIVITY"); 630 * activity.startActivity(intent, TransitionHelper.makeActivityOptions(activity, intent, true); 631 * }</pre> 632 */ 633 @Nullable makeActivityOptions( Activity activity, Intent intent, boolean overrideActivityOptionsFromIntent)634 public static Bundle makeActivityOptions( 635 Activity activity, Intent intent, boolean overrideActivityOptionsFromIntent) { 636 Bundle resultBundle = null; 637 if (activity == null || intent == null) { 638 return resultBundle; 639 } 640 641 if ((intent.getFlags() & Intent.FLAG_ACTIVITY_NEW_TASK) == Intent.FLAG_ACTIVITY_NEW_TASK) { 642 Log.e( 643 TAG, 644 "The transition won't take effect since the WindowManager does not allow override new" 645 + " task transitions"); 646 } 647 648 if (getConfigTransitionType(activity) == CONFIG_TRANSITION_SHARED_X_AXIS) { 649 if (Build.VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { 650 if (activity.getWindow() != null 651 && !activity.getWindow().hasFeature(Window.FEATURE_ACTIVITY_TRANSITIONS)) { 652 Log.w( 653 TAG, 654 "The transition won't take effect due to NO FEATURE_ACTIVITY_TRANSITIONS feature"); 655 } 656 657 if (overrideActivityOptionsFromIntent && activity.getIntent() != null) { 658 resultBundle = activity.getIntent().getBundleExtra(EXTRA_ACTIVITY_OPTIONS); 659 } else { 660 resultBundle = ActivityOptions.makeSceneTransitionAnimation(activity).toBundle(); 661 } 662 intent.putExtra(EXTRA_ACTIVITY_OPTIONS, (Parcelable) resultBundle); 663 return resultBundle; 664 } 665 } 666 667 return resultBundle; 668 } 669 } 670