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 /** Override the transition to a fade-through-from-right (or from-left for RTL locales). */ 131 public static final int TRANSITION_FADE_THROUGH = 6; 132 133 /** 134 * No override. If this is specified as the transition, the enter/exit transition of the window 135 * will not be set and keep original behavior. 136 */ 137 public static final int CONFIG_TRANSITION_NONE = 0; 138 139 /** Override the transition to the specific type that will depend on the partner resource. */ 140 public static final int CONFIG_TRANSITION_SHARED_X_AXIS = 1; 141 142 /** 143 * Passed in an intent as EXTRA_ACTIVITY_OPTIONS. This is the {@link ActivityOptions} of the 144 * transition used in {@link Activity#startActivity} or {@link Activity#startActivityForResult}. 145 */ 146 public static final String EXTRA_ACTIVITY_OPTIONS = "sud:activity_options"; 147 148 /** A flag to avoid the {@link Activity#finish} been called more than once. */ 149 @VisibleForTesting static boolean isFinishCalled = false; 150 151 /** A flag to avoid the {@link Activity#startActivity} called more than once. */ 152 @VisibleForTesting static boolean isStartActivity = false; 153 154 /** A flag to avoid the {@link Activity#startActivityForResult} called more than once. */ 155 @VisibleForTesting static boolean isStartActivityForResult = false; 156 TransitionHelper()157 private TransitionHelper() {} 158 159 /** 160 * Apply the transition for going forward which is decided by {@code Animation.SudWindowAnimation} 161 * theme if the API level is equal or higher than {@link android.os.Build.VERSION_CODES#U}. 162 * 163 * <p>Otherwise, apply the transition for going forward which is decided by partner resource 164 * {@link PartnerConfig#CONFIG_TRANSITION_TYPE} and system property {@code 165 * setupwizard.transition_type} if the API level is equal or lower than {@link 166 * android.os.Build.VERSION_CODES#T}. The default transition that will be applied is {@link 167 * #TRANSITION_SLIDE}. 168 * 169 * <p>The timing to apply the transition is going forward from the previous activity to this, or 170 * going forward from this activity to the next. 171 * 172 * <p>For example, in the flow below, the forward transitions will be applied to all arrows 173 * pointing to the right. Previous screen --> This screen --> Next screen 174 */ 175 @TargetApi(VERSION_CODES.LOLLIPOP) applyForwardTransition(Activity activity)176 public static void applyForwardTransition(Activity activity) { 177 applyForwardTransition(activity, TRANSITION_CAPTIVE); 178 } 179 180 /** 181 * Apply the transition for going forward which is decided by partner resource {@link 182 * PartnerConfig#CONFIG_TRANSITION_TYPE} and system property {@code setupwizard.transition_type}. 183 * The default transition that will be applied is {@link #CONFIG_TRANSITION_NONE}. The timing to 184 * apply the transition is going forward from the previous {@link Fragment} to this, or going 185 * forward from this {@link Fragment} to the next. 186 */ 187 @TargetApi(VERSION_CODES.M) applyForwardTransition(Fragment fragment)188 public static void applyForwardTransition(Fragment fragment) { 189 if (Build.VERSION.SDK_INT >= VERSION_CODES.M) { 190 if (getConfigTransitionType(fragment.getContext()) == CONFIG_TRANSITION_SHARED_X_AXIS) { 191 MaterialSharedAxis exitTransition = 192 new MaterialSharedAxis(MaterialSharedAxis.X, /* forward= */ true); 193 fragment.setExitTransition(exitTransition); 194 195 MaterialSharedAxis enterTransition = 196 new MaterialSharedAxis(MaterialSharedAxis.X, /* forward= */ true); 197 fragment.setEnterTransition(enterTransition); 198 } else { 199 Log.w(TAG, "Not apply the forward transition for platform fragment."); 200 } 201 } else { 202 Log.w( 203 TAG, 204 "Not apply the forward transition for platform fragment. The API is supported from" 205 + " Android Sdk " 206 + VERSION_CODES.M); 207 } 208 } 209 210 /** 211 * Apply the transition for going forward which is decided by {@code Animation.SudWindowAnimation} 212 * theme if the API level is equal or higher than {@link android.os.Build.VERSION_CODES#U}. 213 * 214 * <p>Otherwise, apply the transition for going forward which is decided by the argument {@code 215 * transitionId} if the API level is equal or lower than {@link android.os.Build.VERSION_CODES#T}. 216 * 217 * <p>The timing to apply the transition is going forward from the previous activity to this, or 218 * going forward from this activity to the next. 219 */ 220 @TargetApi(VERSION_CODES.LOLLIPOP) applyForwardTransition(Activity activity, @TransitionType int transitionId)221 public static void applyForwardTransition(Activity activity, @TransitionType int transitionId) { 222 applyForwardTransition(activity, transitionId, /* useClientTransitionSettings= */ false); 223 } 224 225 /** 226 * Apply the transition for going forward which is decided by {@code Animation.SudWindowAnimation} 227 * theme if the API level is equal or higher than {@link android.os.Build.VERSION_CODES#U}, and 228 * argument {@code useClientTransitionSettings} is false, and System property {@code 229 * suw_apply_glif_theme_controlled_transition} is true, and {@code TRANSITION_FADE_THOUGH} 230 * transition is not specified. 231 * 232 * <p>Otherwise, apply the transition for going forward which is decided by the argument {@code 233 * transitionId}, {@code shared_x_axis_activity} transition is used only when {@code 234 * TRANSITION_FADE_TROUGH} transition is specified, and System property {@code * 235 * suw_apply_glif_theme_controlled_transition} is true, and the API level is equal or more than 236 * {@link android.os.Build.VERSION_CODES#U}, other {@code transitionId} can be specified if the 237 * API level is equal or lower than {@link android.os.Build.VERSION_CODES#T}, or argument {@code 238 * useClientTransitionSettings} is true, or System property {@code 239 * suw_apply_glif_theme_controlled_transition} is false. The default transition that will be 240 * applied is {@link #TRANSITION_SLIDE}. 241 * 242 * <p>The timing to apply the transition is going forward from the previous activity to this, or 243 * going forward from this activity to the next. 244 * 245 * <p>For example, in the flow below, the forward transitions will be applied to all arrows 246 * pointing to the right. Previous screen --> This screen --> Next screen 247 */ 248 @TargetApi(VERSION_CODES.LOLLIPOP) applyForwardTransition( Activity activity, @TransitionType int transitionId, boolean useClientTransitionSettings)249 public static void applyForwardTransition( 250 Activity activity, @TransitionType int transitionId, boolean useClientTransitionSettings) { 251 if (BuildCompatUtils.isAtLeastU() 252 && !useClientTransitionSettings 253 && PartnerConfigHelper.isGlifThemeControlledTransitionApplied(activity) 254 && transitionId != TRANSITION_FADE_THROUGH) { 255 // Do nothing 256 } else if (BuildCompatUtils.isAtLeastU() && transitionId == TRANSITION_FADE_THROUGH) { 257 int openEnterTransition = R.anim.shared_x_axis_activity_open_enter; 258 if (PartnerConfigHelper.isGlifThemeControlledTransitionApplied(activity)) { 259 if (ThemeHelper.shouldApplyDynamicColor(activity)) { 260 openEnterTransition = R.anim.shared_x_axis_activity_open_enter_dynamic_color; 261 } 262 activity.overridePendingTransition( 263 openEnterTransition, R.anim.shared_x_axis_activity_open_exit); 264 } else { 265 activity.overridePendingTransition(R.anim.sud_slide_next_in, R.anim.sud_slide_next_out); 266 } 267 } else if (transitionId == TRANSITION_SLIDE) { 268 activity.overridePendingTransition(R.anim.sud_slide_next_in, R.anim.sud_slide_next_out); 269 } else if (transitionId == TRANSITION_FADE) { 270 activity.overridePendingTransition(android.R.anim.fade_in, R.anim.sud_stay); 271 } else if (transitionId == TRANSITION_FRAMEWORK_DEFAULT) { 272 TypedArray typedArray = 273 activity.obtainStyledAttributes( 274 android.R.style.Animation_Activity, 275 new int[] { 276 android.R.attr.activityOpenEnterAnimation, android.R.attr.activityOpenExitAnimation 277 }); 278 activity.overridePendingTransition( 279 typedArray.getResourceId(/* index= */ 0, /* defValue= */ 0), 280 typedArray.getResourceId(/* index= */ 1, /* defValue= */ 0)); 281 typedArray.recycle(); 282 } else if (transitionId == TRANSITION_FRAMEWORK_DEFAULT_PRE_P) { 283 activity.overridePendingTransition( 284 R.anim.sud_pre_p_activity_open_enter, R.anim.sud_pre_p_activity_open_exit); 285 } else if (transitionId == TRANSITION_NONE) { 286 // For TRANSITION_NONE, turn off the transition 287 activity.overridePendingTransition(/* enterAnim= */ 0, /* exitAnim= */ 0); 288 } else if (transitionId == TRANSITION_CAPTIVE) { 289 if (Build.VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { 290 // 1. Do not change the transition behavior by default 291 // 2. If the flag present, apply the transition from transition type 292 if (getConfigTransitionType(activity) == CONFIG_TRANSITION_SHARED_X_AXIS) { 293 Window window = activity.getWindow(); 294 if (window != null) { 295 MaterialSharedAxis exitTransition = 296 new MaterialSharedAxis(MaterialSharedAxis.X, /* forward= */ true); 297 window.setExitTransition(exitTransition); 298 299 window.setAllowEnterTransitionOverlap(true); 300 301 MaterialSharedAxis enterTransition = 302 new MaterialSharedAxis(MaterialSharedAxis.X, /* forward= */ true); 303 window.setEnterTransition(enterTransition); 304 } else { 305 Log.w(TAG, "applyForwardTransition: Invalid window=" + window); 306 } 307 } 308 } else { 309 Log.w(TAG, "This API is supported from Android Sdk " + VERSION_CODES.LOLLIPOP); 310 } 311 } 312 // For TRANSITION_NO_OVERRIDE or other values, do not override the transition 313 } 314 315 /** 316 * Apply the transition for going backward which is decided by {@code 317 * Animation.SudWindowAnimation} theme if the API level is equal or higher than {@link 318 * android.os.Build.VERSION_CODES#U}. 319 * 320 * <p>Otherwise, apply the transition for going backward which is decided by partner resource 321 * {@link PartnerConfig#CONFIG_TRANSITION_TYPE} and system property {@code 322 * setupwizard.transition_type} if the API level is equal or lower than {@link 323 * android.os.Build.VERSION_CODES#T}. The default transition that will be applied is {@link 324 * #TRANSITION_SLIDE}. 325 * 326 * <p>The timing to apply the transition is going backward from the next activity to this, or 327 * going backward from this activity to the previous. 328 * 329 * <p>For example, in the flow below, the backward transitions will be applied to all arrows 330 * pointing to the left. Previous screen <-- This screen <-- Next screen. 331 */ 332 @TargetApi(VERSION_CODES.LOLLIPOP) applyBackwardTransition(Activity activity)333 public static void applyBackwardTransition(Activity activity) { 334 applyBackwardTransition(activity, TRANSITION_CAPTIVE); 335 } 336 337 /** 338 * Apply the transition for going backward which is decided by partner resource {@link 339 * PartnerConfig#CONFIG_TRANSITION_TYPE} and system property {@code setupwizard.transition_type}. 340 * The default transition that will be applied is {@link #CONFIG_TRANSITION_NONE}. The timing to 341 * apply the transition is going backward from the next {@link Fragment} to this, or going 342 * backward from this {@link Fragment} to the previous. 343 */ 344 @TargetApi(VERSION_CODES.M) applyBackwardTransition(Fragment fragment)345 public static void applyBackwardTransition(Fragment fragment) { 346 if (Build.VERSION.SDK_INT >= VERSION_CODES.M) { 347 if (getConfigTransitionType(fragment.getContext()) == CONFIG_TRANSITION_SHARED_X_AXIS) { 348 MaterialSharedAxis returnTransition = 349 new MaterialSharedAxis(MaterialSharedAxis.X, /* forward= */ false); 350 fragment.setReturnTransition(returnTransition); 351 352 MaterialSharedAxis reenterTransition = 353 new MaterialSharedAxis(MaterialSharedAxis.X, /* forward= */ false); 354 fragment.setReenterTransition(reenterTransition); 355 } else { 356 Log.w(TAG, "Not apply the backward transition for platform fragment."); 357 } 358 } else { 359 Log.w( 360 TAG, 361 "Not apply the backward transition for platform fragment. The API is supported from" 362 + " Android Sdk " 363 + VERSION_CODES.M); 364 } 365 } 366 367 /** 368 * Apply the transition for going backward which is decided by {@code 369 * Animation.SudWindowAnimation} theme if the API level is equal or higher than {@link 370 * android.os.Build.VERSION_CODES#U}. 371 * 372 * <p>Otherwise, apply the transition for going backward which is decided by the argument {@code 373 * transitionId} if the API level is equal or lower than {@link android.os.Build.VERSION_CODES#T}. 374 * 375 * <p>The timing to apply the transition is going backward from the next activity to this, or 376 * going backward from this activity to the previous. 377 */ 378 @TargetApi(VERSION_CODES.LOLLIPOP) applyBackwardTransition(Activity activity, @TransitionType int transitionId)379 public static void applyBackwardTransition(Activity activity, @TransitionType int transitionId) { 380 applyBackwardTransition(activity, transitionId, /* useClientTransitionSettings= */ false); 381 } 382 383 /** 384 * Apply the transition for going backward which is decided by {@code 385 * Animation.SudWindowAnimation} theme if the API level is equal or higher than {@link 386 * android.os.Build.VERSION_CODES#U}, and argument {@code useClientTransitionSettings} is false, 387 * and System property {@code suw_apply_glif_theme_controlled_transition} is true, and {@code 388 * TRANSITION_FADE_THOUGH} transition is not specified. 389 * 390 * <p>Otherwise, apply the transition for going backward which is decided by the argument {@code 391 * transitionId}, {@code shared_x_axis_activity} transition is used only when {@code 392 * TRANSITION_FADE_TROUGH} transition is specified, and System property {@code * 393 * suw_apply_glif_theme_controlled_transition} is true, and the API level is equal or more than 394 * {@link android.os.Build.VERSION_CODES#U}, other {@code transitionId} can be specified if the 395 * API level is equal or lower than {@link android.os.Build.VERSION_CODES#T}, or argument {@code 396 * useClientTransitionSettings} is true, or System property {@code 397 * suw_apply_glif_theme_controlled_transition} is false. The default transition that will be 398 * applied is {@link #TRANSITION_SLIDE}. 399 * 400 * <p>The timing to apply the transition is going backward from the next activity to this, or 401 * going backward from this activity to the previous. 402 * 403 * <p>For example, in the flow below, the backward transitions will be applied to all arrows 404 * pointing to the left. Previous screen <-- This screen <-- Next screen 405 */ 406 @TargetApi(VERSION_CODES.LOLLIPOP) applyBackwardTransition( Activity activity, @TransitionType int transitionId, boolean useClientTransitionSettings)407 public static void applyBackwardTransition( 408 Activity activity, @TransitionType int transitionId, boolean useClientTransitionSettings) { 409 if (BuildCompatUtils.isAtLeastU() 410 && !useClientTransitionSettings 411 && PartnerConfigHelper.isGlifThemeControlledTransitionApplied(activity) 412 && transitionId != TRANSITION_FADE_THROUGH) { 413 // Do nothing 414 } else if (BuildCompatUtils.isAtLeastU() && transitionId == TRANSITION_FADE_THROUGH) { 415 if (PartnerConfigHelper.isGlifThemeControlledTransitionApplied(activity)) { 416 int closeEnterTransition = R.anim.shared_x_axis_activity_close_enter; 417 if (ThemeHelper.shouldApplyDynamicColor(activity)) { 418 closeEnterTransition = R.anim.shared_x_axis_activity_close_enter_dynamic_color; 419 } 420 activity.overridePendingTransition( 421 closeEnterTransition, R.anim.shared_x_axis_activity_close_exit); 422 } else { 423 activity.overridePendingTransition(R.anim.sud_slide_back_in, R.anim.sud_slide_back_out); 424 } 425 } else if (transitionId == TRANSITION_SLIDE) { 426 activity.overridePendingTransition(R.anim.sud_slide_back_in, R.anim.sud_slide_back_out); 427 } else if (transitionId == TRANSITION_FADE) { 428 activity.overridePendingTransition(R.anim.sud_stay, android.R.anim.fade_out); 429 } else if (transitionId == TRANSITION_FRAMEWORK_DEFAULT) { 430 TypedArray typedArray = 431 activity.obtainStyledAttributes( 432 android.R.style.Animation_Activity, 433 new int[] { 434 android.R.attr.activityCloseEnterAnimation, 435 android.R.attr.activityCloseExitAnimation 436 }); 437 activity.overridePendingTransition( 438 typedArray.getResourceId(/* index= */ 0, /* defValue= */ 0), 439 typedArray.getResourceId(/* index= */ 1, /* defValue= */ 0)); 440 typedArray.recycle(); 441 } else if (transitionId == TRANSITION_FRAMEWORK_DEFAULT_PRE_P) { 442 activity.overridePendingTransition( 443 R.anim.sud_pre_p_activity_close_enter, R.anim.sud_pre_p_activity_close_exit); 444 } else if (transitionId == TRANSITION_NONE) { 445 // For TRANSITION_NONE, turn off the transition 446 activity.overridePendingTransition(/* enterAnim= */ 0, /* exitAnim= */ 0); 447 } else if (transitionId == TRANSITION_CAPTIVE) { 448 if (Build.VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { 449 // 1. Do not change the transition behavior by default 450 // 2. If the flag present, apply the transition from transition type 451 if (getConfigTransitionType(activity) == CONFIG_TRANSITION_SHARED_X_AXIS) { 452 Window window = activity.getWindow(); 453 if (window != null) { 454 MaterialSharedAxis reenterTransition = 455 new MaterialSharedAxis(MaterialSharedAxis.X, /* forward= */ false); 456 window.setReenterTransition(reenterTransition); 457 458 MaterialSharedAxis returnTransition = 459 new MaterialSharedAxis(MaterialSharedAxis.X, /* forward= */ false); 460 window.setReturnTransition(returnTransition); 461 } else { 462 Log.w(TAG, "applyBackwardTransition: Invalid window=" + window); 463 } 464 } 465 } else { 466 Log.w(TAG, "This API is supported from Android Sdk " + VERSION_CODES.LOLLIPOP); 467 } 468 // For TRANSITION_NO_OVERRIDE or other values, do not override the transition 469 } 470 } 471 472 /** 473 * A wrapper method, create an {@link android.app.ActivityOptions} to transition between 474 * activities as the {@link ActivityOptions} parameter of {@link Activity#startActivity}. 475 * 476 * @throws IllegalArgumentException is thrown when {@code activity} or {@code intent} is null. 477 * @throws android.content.ActivityNotFoundException if there was no {@link Activity} found to run 478 * the given Intent. 479 */ startActivityWithTransition(Activity activity, Intent intent)480 public static void startActivityWithTransition(Activity activity, Intent intent) { 481 startActivityWithTransition(activity, intent, /* overrideActivityOptions= */ null); 482 } 483 484 /** 485 * A wrapper method, create an {@link android.app.ActivityOptions} to transition between 486 * activities as the {@link ActivityOptions} parameter of {@link Activity#startActivity}. 487 * 488 * @throws IllegalArgumentException is thrown when {@code activity} or {@code intent} is null. 489 * @throws android.content.ActivityNotFoundException if there was no {@link Activity} found to run 490 * the given Intent. 491 */ startActivityWithTransition( Activity activity, Intent intent, Bundle overrideActivityOptions)492 public static void startActivityWithTransition( 493 Activity activity, Intent intent, Bundle overrideActivityOptions) { 494 if (activity == null) { 495 throw new IllegalArgumentException("Invalid activity=" + activity); 496 } 497 498 if (intent == null) { 499 throw new IllegalArgumentException("Invalid intent=" + intent); 500 } 501 502 if ((intent.getFlags() & Intent.FLAG_ACTIVITY_NEW_TASK) == Intent.FLAG_ACTIVITY_NEW_TASK) { 503 Log.e( 504 TAG, 505 "The transition won't take effect since the WindowManager does not allow override new" 506 + " task transitions"); 507 } 508 509 if (!isStartActivity) { 510 isStartActivity = true; 511 if (getConfigTransitionType(activity) == CONFIG_TRANSITION_SHARED_X_AXIS) { 512 if (Build.VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { 513 if (activity.getWindow() != null 514 && !activity.getWindow().hasFeature(Window.FEATURE_ACTIVITY_TRANSITIONS)) { 515 Log.w( 516 TAG, 517 "The transition won't take effect due to NO FEATURE_ACTIVITY_TRANSITIONS feature"); 518 } 519 520 Bundle bundleActivityOptions; 521 if (overrideActivityOptions != null) { 522 bundleActivityOptions = overrideActivityOptions; 523 } else { 524 bundleActivityOptions = makeActivityOptions(activity, intent); 525 } 526 intent.putExtra(EXTRA_ACTIVITY_OPTIONS, (Parcelable) bundleActivityOptions); 527 activity.startActivity(intent, bundleActivityOptions); 528 } else { 529 Log.w( 530 TAG, 531 "Fallback to using startActivity due to the" 532 + " ActivityOptions#makeSceneTransitionAnimation is supported from Android Sdk " 533 + VERSION_CODES.LOLLIPOP); 534 startActivityWithTransitionInternal(activity, intent, overrideActivityOptions); 535 } 536 } else { 537 startActivityWithTransitionInternal(activity, intent, overrideActivityOptions); 538 } 539 } 540 isStartActivity = false; 541 } 542 startActivityWithTransitionInternal( Activity activity, Intent intent, Bundle overrideActivityOptions)543 private static void startActivityWithTransitionInternal( 544 Activity activity, Intent intent, Bundle overrideActivityOptions) { 545 try { 546 if (Build.VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN) { 547 if (getConfigTransitionType(activity) == CONFIG_TRANSITION_SHARED_X_AXIS 548 && overrideActivityOptions != null) { 549 activity.startActivity(intent, overrideActivityOptions); 550 } else { 551 activity.startActivity(intent); 552 } 553 } else { 554 Log.w( 555 TAG, 556 "Fallback to using startActivity(Intent) due to the startActivity(Intent, Bundle) is" 557 + " supported from Android Sdk " 558 + VERSION_CODES.JELLY_BEAN); 559 activity.startActivity(intent); 560 } 561 } catch (ActivityNotFoundException e) { 562 Log.w(TAG, "Activity not found when startActivity with transition."); 563 isStartActivity = false; 564 throw e; 565 } 566 } 567 startActivityForResultWithTransition( Activity activity, Intent intent, int requestCode)568 public static void startActivityForResultWithTransition( 569 Activity activity, Intent intent, int requestCode) { 570 startActivityForResultWithTransition( 571 activity, intent, requestCode, /* overrideActivityOptions= */ null); 572 } 573 574 /** 575 * A wrapper method, create an {@link android.app.ActivityOptions} to transition between 576 * activities as the {@code activityOptions} parameter of {@link Activity#startActivityForResult}. 577 * 578 * @throws IllegalArgumentException is thrown when {@code activity} or {@code intent} is null. 579 * @throws android.content.ActivityNotFoundException if there was no {@link Activity} found to run 580 * the given Intent. 581 */ startActivityForResultWithTransition( Activity activity, Intent intent, int requestCode, Bundle overrideActivityOptions)582 public static void startActivityForResultWithTransition( 583 Activity activity, Intent intent, int requestCode, Bundle overrideActivityOptions) { 584 if (activity == null) { 585 throw new IllegalArgumentException("Invalid activity=" + activity); 586 } 587 588 if (intent == null) { 589 throw new IllegalArgumentException("Invalid intent=" + intent); 590 } 591 592 if ((intent.getFlags() & Intent.FLAG_ACTIVITY_NEW_TASK) == Intent.FLAG_ACTIVITY_NEW_TASK) { 593 Log.e( 594 TAG, 595 "The transition won't take effect since the WindowManager does not allow override new" 596 + " task transitions"); 597 } 598 599 if (!isStartActivityForResult) { 600 isStartActivityForResult = true; 601 if (getConfigTransitionType(activity) == CONFIG_TRANSITION_SHARED_X_AXIS) { 602 if (Build.VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { 603 if (activity.getWindow() != null 604 && !activity.getWindow().hasFeature(Window.FEATURE_ACTIVITY_TRANSITIONS)) { 605 Log.w( 606 TAG, 607 "The transition won't take effect due to NO FEATURE_ACTIVITY_TRANSITIONS feature"); 608 } 609 610 Bundle bundleActivityOptions; 611 if (overrideActivityOptions != null) { 612 bundleActivityOptions = overrideActivityOptions; 613 } else { 614 bundleActivityOptions = makeActivityOptions(activity, intent); 615 } 616 intent.putExtra(EXTRA_ACTIVITY_OPTIONS, (Parcelable) bundleActivityOptions); 617 activity.startActivityForResult(intent, requestCode, bundleActivityOptions); 618 } else { 619 Log.w( 620 TAG, 621 "Fallback to using startActivityForResult API due to the" 622 + " ActivityOptions#makeSceneTransitionAnimation is supported from Android Sdk " 623 + VERSION_CODES.LOLLIPOP); 624 startActivityForResultWithTransitionInternal( 625 activity, intent, requestCode, overrideActivityOptions); 626 } 627 } else { 628 startActivityForResultWithTransitionInternal( 629 activity, intent, requestCode, overrideActivityOptions); 630 } 631 isStartActivityForResult = false; 632 } 633 } 634 startActivityForResultWithTransitionInternal( Activity activity, Intent intent, int requestCode, Bundle overrideActivityOptions)635 private static void startActivityForResultWithTransitionInternal( 636 Activity activity, Intent intent, int requestCode, Bundle overrideActivityOptions) { 637 try { 638 if (Build.VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN) { 639 if (getConfigTransitionType(activity) == CONFIG_TRANSITION_SHARED_X_AXIS 640 && overrideActivityOptions != null) { 641 activity.startActivityForResult(intent, requestCode, overrideActivityOptions); 642 } else { 643 activity.startActivityForResult(intent, requestCode); 644 } 645 } else { 646 Log.w( 647 TAG, 648 "Fallback to using startActivityForResult(Intent) due to the" 649 + " startActivityForResult(Intent,int) is supported from Android Sdk " 650 + VERSION_CODES.JELLY_BEAN); 651 activity.startActivityForResult(intent, requestCode); 652 } 653 } catch (ActivityNotFoundException e) { 654 Log.w(TAG, "Activity not found when startActivityForResult with transition."); 655 isStartActivityForResult = false; 656 throw e; 657 } 658 } 659 660 /** 661 * A wrapper method, calling {@link Activity#finishAfterTransition()} to trigger exit transition 662 * when running in Android S and the transition type {link #CONFIG_TRANSITION_SHARED_X_AXIS}. 663 * 664 * @throws IllegalArgumentException is thrown when {@code activity} is null. 665 */ finishActivity(Activity activity)666 public static void finishActivity(Activity activity) { 667 if (activity == null) { 668 throw new IllegalArgumentException("Invalid activity=" + activity); 669 } 670 671 // Avoids finish been called more than once. 672 if (!isFinishCalled) { 673 isFinishCalled = true; 674 if (Build.VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP 675 && getConfigTransitionType(activity) == CONFIG_TRANSITION_SHARED_X_AXIS) { 676 activity.finishAfterTransition(); 677 } else { 678 Log.w( 679 TAG, 680 "Fallback to using Activity#finish() due to the" 681 + " Activity#finishAfterTransition() is supported from Android Sdk " 682 + VERSION_CODES.LOLLIPOP); 683 activity.finish(); 684 } 685 } 686 isFinishCalled = false; 687 } 688 689 /** 690 * Returns the transition type from the {@link PartnerConfig#CONFIG_TRANSITION_TYPE} partner 691 * resource on Android S, otherwise returns {@link #CONFIG_TRANSITION_NONE}. 692 */ getConfigTransitionType(Context context)693 public static int getConfigTransitionType(Context context) { 694 return BuildCompatUtils.isAtLeastS() && ThemeHelper.shouldApplyExtendedPartnerConfig(context) 695 ? PartnerConfigHelper.get(context) 696 .getInteger(context, PartnerConfig.CONFIG_TRANSITION_TYPE, CONFIG_TRANSITION_NONE) 697 : CONFIG_TRANSITION_NONE; 698 } 699 700 /** 701 * A wrapper method, create a {@link Bundle} from {@link ActivityOptions} to transition between 702 * Activities using cross-Activity scene animations. This {@link Bundle} that can be used with 703 * {@link Context#startActivity(Intent, Bundle)} and related methods. 704 * 705 * <p>Example usage: 706 * 707 * <pre>{@code 708 * Intent intent = new Intent("com.example.NEXT_ACTIVITY"); 709 * activity.startActivity(intent, TransitionHelper.makeActivityOptions(activity, intent, null); 710 * }</pre> 711 * 712 * <p>Unexpected usage: 713 * 714 * <pre>{@code 715 * Intent intent = new Intent("com.example.NEXT_ACTIVITY"); 716 * Intent intent2 = new Intent("com.example.NEXT_ACTIVITY"); 717 * activity.startActivity(intent, TransitionHelper.makeActivityOptions(activity, intent2, null); 718 * }</pre> 719 */ 720 @Nullable makeActivityOptions(Activity activity, Intent intent)721 public static Bundle makeActivityOptions(Activity activity, Intent intent) { 722 return makeActivityOptions(activity, intent, false); 723 } 724 725 /** 726 * A wrapper method, create a {@link Bundle} from {@link ActivityOptions} to transition between 727 * Activities using cross-Activity scene animations. This {@link Bundle} that can be used with 728 * {@link Context#startActivity(Intent, Bundle)} and related methods. When this {@code activity} 729 * is a no UI activity(the activity doesn't inflate any layouts), you will need to pass the bundle 730 * coming from previous UI activity as the {@link ActivityOptions}, otherwise, the transition 731 * won't be take effect. The {@code overrideActivityOptionsFromIntent} is supporting this purpose 732 * to return the {@link ActivityOptions} instead of creating from this no UI activity while the 733 * transition is apply {@link #CONFIG_TRANSITION_SHARED_X_AXIS} config. Moreover, the 734 * startActivity*WithTransition relative methods and {@link #makeActivityOptions} will put {@link 735 * ActivityOptions} to the {@code intent} by default, you can get the {@link ActivityOptions} 736 * which makes from previous activity by accessing {@link #EXTRA_ACTIVITY_OPTIONS} extra from 737 * {@link Activity#getIntent()}. 738 * 739 * <p>Example usage of a no UI activity: 740 * 741 * <pre>{@code 742 * Intent intent = new Intent("com.example.NEXT_ACTIVITY"); 743 * activity.startActivity(intent, TransitionHelper.makeActivityOptions(activity, intent, true); 744 * }</pre> 745 */ 746 @Nullable makeActivityOptions( Activity activity, Intent intent, boolean overrideActivityOptionsFromIntent)747 public static Bundle makeActivityOptions( 748 Activity activity, Intent intent, boolean overrideActivityOptionsFromIntent) { 749 Bundle resultBundle = null; 750 if (activity == null || intent == null) { 751 return resultBundle; 752 } 753 754 if ((intent.getFlags() & Intent.FLAG_ACTIVITY_NEW_TASK) == Intent.FLAG_ACTIVITY_NEW_TASK) { 755 Log.e( 756 TAG, 757 "The transition won't take effect since the WindowManager does not allow override new" 758 + " task transitions"); 759 } 760 761 if (getConfigTransitionType(activity) == CONFIG_TRANSITION_SHARED_X_AXIS) { 762 if (Build.VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { 763 if (activity.getWindow() != null 764 && !activity.getWindow().hasFeature(Window.FEATURE_ACTIVITY_TRANSITIONS)) { 765 Log.w( 766 TAG, 767 "The transition won't take effect due to NO FEATURE_ACTIVITY_TRANSITIONS feature"); 768 } 769 770 if (overrideActivityOptionsFromIntent && activity.getIntent() != null) { 771 resultBundle = activity.getIntent().getBundleExtra(EXTRA_ACTIVITY_OPTIONS); 772 } else { 773 resultBundle = ActivityOptions.makeSceneTransitionAnimation(activity).toBundle(); 774 } 775 intent.putExtra(EXTRA_ACTIVITY_OPTIONS, (Parcelable) resultBundle); 776 return resultBundle; 777 } 778 } 779 780 return resultBundle; 781 } 782 } 783