1 /* 2 * Copyright (C) 2012 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 androidx.core.app; 18 19 import android.annotation.SuppressLint; 20 import android.app.Activity; 21 import android.app.ActivityOptions; 22 import android.app.PendingIntent; 23 import android.content.Context; 24 import android.content.Intent; 25 import android.graphics.Bitmap; 26 import android.graphics.Rect; 27 import android.os.Build; 28 import android.os.Bundle; 29 import android.view.Display; 30 import android.view.View; 31 32 import androidx.annotation.IntDef; 33 import androidx.annotation.RestrictTo; 34 import androidx.core.util.Pair; 35 36 import org.jspecify.annotations.NonNull; 37 import org.jspecify.annotations.Nullable; 38 39 import java.lang.annotation.Retention; 40 import java.lang.annotation.RetentionPolicy; 41 42 /** 43 * Helper for accessing features in {@link android.app.ActivityOptions} in a backwards compatible 44 * fashion. 45 */ 46 public class ActivityOptionsCompat { 47 /** 48 * A long in the extras delivered by {@link #requestUsageTimeReport} that contains 49 * the total time (in ms) the user spent in the app flow. 50 */ 51 public static final String EXTRA_USAGE_TIME_REPORT = "android.activity.usage_time"; 52 53 /** 54 * A Bundle in the extras delivered by {@link #requestUsageTimeReport} that contains 55 * detailed information about the time spent in each package associated with the app; 56 * each key is a package name, whose value is a long containing the time (in ms). 57 */ 58 public static final String EXTRA_USAGE_TIME_REPORT_PACKAGES = "android.usage_time_packages"; 59 60 /** 61 * Enumeration of background activity start modes. 62 * <p/> 63 * These define if an app wants to grant it's background activity start privileges to a 64 * {@link PendingIntent}. 65 */ 66 @Retention(RetentionPolicy.SOURCE) 67 @RestrictTo(RestrictTo.Scope.LIBRARY) 68 @IntDef(value = { 69 ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED, 70 ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED, 71 ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED}) 72 public @interface BackgroundActivityStartMode {} 73 74 /** 75 * Create an ActivityOptions specifying a custom animation to run when the 76 * activity is displayed. 77 * 78 * @param context Who is defining this. This is the application that the 79 * animation resources will be loaded from. 80 * @param enterResId A resource ID of the animation resource to use for the 81 * incoming activity. Use 0 for no animation. 82 * @param exitResId A resource ID of the animation resource to use for the 83 * outgoing activity. Use 0 for no animation. 84 * @return Returns a new ActivityOptions object that you can use to supply 85 * these options as the options Bundle when starting an activity. 86 */ makeCustomAnimation(@onNull Context context, int enterResId, int exitResId)87 public static @NonNull ActivityOptionsCompat makeCustomAnimation(@NonNull Context context, 88 int enterResId, int exitResId) { 89 return new ActivityOptionsCompatImpl( 90 ActivityOptions.makeCustomAnimation(context, enterResId, exitResId)); 91 } 92 93 /** 94 * Create an ActivityOptions specifying an animation where the new activity is 95 * scaled from a small originating area of the screen to its final full 96 * representation. 97 * <p/> 98 * If the Intent this is being used with has not set its 99 * {@link android.content.Intent#setSourceBounds(android.graphics.Rect)}, 100 * those bounds will be filled in for you based on the initial bounds passed 101 * in here. 102 * 103 * @param source The View that the new activity is animating from. This 104 * defines the coordinate space for startX and startY. 105 * @param startX The x starting location of the new activity, relative to 106 * source. 107 * @param startY The y starting location of the activity, relative to source. 108 * @param startWidth The initial width of the new activity. 109 * @param startHeight The initial height of the new activity. 110 * @return Returns a new ActivityOptions object that you can use to supply 111 * these options as the options Bundle when starting an activity. 112 */ makeScaleUpAnimation(@onNull View source, int startX, int startY, int startWidth, int startHeight)113 public static @NonNull ActivityOptionsCompat makeScaleUpAnimation(@NonNull View source, 114 int startX, int startY, int startWidth, int startHeight) { 115 return new ActivityOptionsCompatImpl( 116 ActivityOptions.makeScaleUpAnimation(source, startX, startY, startWidth, 117 startHeight)); 118 } 119 120 /** 121 * Create an ActivityOptions specifying an animation where the new 122 * activity is revealed from a small originating area of the screen to 123 * its final full representation. 124 * 125 * @param source The View that the new activity is animating from. This 126 * defines the coordinate space for <var>startX</var> and <var>startY</var>. 127 * @param startX The x starting location of the new activity, relative to <var>source</var>. 128 * @param startY The y starting location of the activity, relative to <var>source</var>. 129 * @param width The initial width of the new activity. 130 * @param height The initial height of the new activity. 131 * @return Returns a new ActivityOptions object that you can use to 132 * supply these options as the options Bundle when starting an activity. 133 */ makeClipRevealAnimation(@onNull View source, int startX, int startY, int width, int height)134 public static @NonNull ActivityOptionsCompat makeClipRevealAnimation(@NonNull View source, 135 int startX, int startY, int width, int height) { 136 if (Build.VERSION.SDK_INT >= 23) { 137 return new ActivityOptionsCompatImpl( 138 ActivityOptions.makeClipRevealAnimation(source, startX, startY, width, height)); 139 } 140 return new ActivityOptionsCompat(); 141 } 142 143 /** 144 * Create an ActivityOptions specifying an animation where a thumbnail is 145 * scaled from a given position to the new activity window that is being 146 * started. 147 * <p/> 148 * If the Intent this is being used with has not set its 149 * {@link android.content.Intent#setSourceBounds(android.graphics.Rect)}, 150 * those bounds will be filled in for you based on the initial thumbnail 151 * location and size provided here. 152 * 153 * @param source The View that this thumbnail is animating from. This 154 * defines the coordinate space for startX and startY. 155 * @param thumbnail The bitmap that will be shown as the initial thumbnail 156 * of the animation. 157 * @param startX The x starting location of the bitmap, relative to source. 158 * @param startY The y starting location of the bitmap, relative to source. 159 * @return Returns a new ActivityOptions object that you can use to supply 160 * these options as the options Bundle when starting an activity. 161 */ makeThumbnailScaleUpAnimation(@onNull View source, @NonNull Bitmap thumbnail, int startX, int startY)162 public static @NonNull ActivityOptionsCompat makeThumbnailScaleUpAnimation(@NonNull View source, 163 @NonNull Bitmap thumbnail, int startX, int startY) { 164 return new ActivityOptionsCompatImpl( 165 ActivityOptions.makeThumbnailScaleUpAnimation(source, thumbnail, startX, startY)); 166 } 167 168 /** 169 * Create an ActivityOptions to transition between Activities using cross-Activity scene 170 * animations. This method carries the position of one shared element to the started Activity. 171 * The position of <code>sharedElement</code> will be used as the epicenter for the 172 * exit Transition. The position of the shared element in the launched Activity will be the 173 * epicenter of its entering Transition. 174 * 175 * <p>This requires {@link android.view.Window#FEATURE_CONTENT_TRANSITIONS} to be 176 * enabled on the calling Activity to cause an exit transition. The same must be in 177 * the called Activity to get an entering transition.</p> 178 * @param activity The Activity whose window contains the shared elements. 179 * @param sharedElement The View to transition to the started Activity. sharedElement must 180 * have a non-null sharedElementName. 181 * @param sharedElementName The shared element name as used in the target Activity. This may 182 * be null if it has the same name as sharedElement. 183 * @return Returns a new ActivityOptions object that you can use to 184 * supply these options as the options Bundle when starting an activity. 185 */ makeSceneTransitionAnimation( @onNull Activity activity, @NonNull View sharedElement, @NonNull String sharedElementName)186 public static @NonNull ActivityOptionsCompat makeSceneTransitionAnimation( 187 @NonNull Activity activity, @NonNull View sharedElement, 188 @NonNull String sharedElementName) { 189 return new ActivityOptionsCompatImpl( 190 ActivityOptions.makeSceneTransitionAnimation(activity, sharedElement, 191 sharedElementName)); 192 } 193 194 /** 195 * Create an ActivityOptions to transition between Activities using cross-Activity scene 196 * animations. This method carries the position of multiple shared elements to the started 197 * Activity. The position of the first element in sharedElements 198 * will be used as the epicenter for the exit Transition. The position of the associated 199 * shared element in the launched Activity will be the epicenter of its entering Transition. 200 * 201 * <p>This requires {@link android.view.Window#FEATURE_CONTENT_TRANSITIONS} to be 202 * enabled on the calling Activity to cause an exit transition. The same must be in 203 * the called Activity to get an entering transition.</p> 204 * @param activity The Activity whose window contains the shared elements. 205 * @param sharedElements The names of the shared elements to transfer to the called 206 * Activity and their associated Views. The Views must each have 207 * a unique shared element name. 208 * @return Returns a new ActivityOptions object that you can use to 209 * supply these options as the options Bundle when starting an activity. 210 */ 211 @SuppressWarnings("unchecked") makeSceneTransitionAnimation( @onNull Activity activity, Pair<View, String> @Nullable ... sharedElements)212 public static @NonNull ActivityOptionsCompat makeSceneTransitionAnimation( 213 @NonNull Activity activity, Pair<View, String> @Nullable ... sharedElements) { 214 android.util.Pair<View, String>[] pairs = null; 215 if (sharedElements != null) { 216 pairs = new android.util.Pair[sharedElements.length]; 217 for (int i = 0; i < sharedElements.length; i++) { 218 pairs[i] = android.util.Pair.create( 219 sharedElements[i].first, sharedElements[i].second); 220 } 221 } 222 return new ActivityOptionsCompatImpl( 223 ActivityOptions.makeSceneTransitionAnimation(activity, pairs)); 224 } 225 226 /** 227 * If set along with Intent.FLAG_ACTIVITY_NEW_DOCUMENT then the task being launched will not be 228 * presented to the user but will instead be only available through the recents task list. 229 * In addition, the new task wil be affiliated with the launching activity's task. 230 * Affiliated tasks are grouped together in the recents task list. 231 * 232 * <p>This behavior is not supported for activities with 233 * {@link android.R.attr#launchMode launchMode} values of 234 * <code>singleInstance</code> or <code>singleTask</code>. 235 */ makeTaskLaunchBehind()236 public static @NonNull ActivityOptionsCompat makeTaskLaunchBehind() { 237 return new ActivityOptionsCompatImpl(ActivityOptions.makeTaskLaunchBehind()); 238 } 239 240 /** 241 * Create a basic ActivityOptions that has no special animation associated with it. 242 * Other options can still be set. 243 */ makeBasic()244 public static @NonNull ActivityOptionsCompat makeBasic() { 245 if (Build.VERSION.SDK_INT >= 23) { 246 return new ActivityOptionsCompatImpl(ActivityOptions.makeBasic()); 247 } 248 return new ActivityOptionsCompat(); 249 } 250 251 private static class ActivityOptionsCompatImpl extends ActivityOptionsCompat { 252 private final ActivityOptions mActivityOptions; 253 ActivityOptionsCompatImpl(ActivityOptions activityOptions)254 ActivityOptionsCompatImpl(ActivityOptions activityOptions) { 255 mActivityOptions = activityOptions; 256 } 257 258 @Override toBundle()259 public Bundle toBundle() { 260 return mActivityOptions.toBundle(); 261 } 262 263 @Override update(@onNull ActivityOptionsCompat otherOptions)264 public void update(@NonNull ActivityOptionsCompat otherOptions) { 265 if (otherOptions instanceof ActivityOptionsCompatImpl) { 266 ActivityOptionsCompatImpl otherImpl = 267 (ActivityOptionsCompatImpl) otherOptions; 268 mActivityOptions.update(otherImpl.mActivityOptions); 269 } 270 } 271 272 @Override requestUsageTimeReport(@onNull PendingIntent receiver)273 public void requestUsageTimeReport(@NonNull PendingIntent receiver) { 274 if (Build.VERSION.SDK_INT >= 23) { 275 mActivityOptions.requestUsageTimeReport(receiver); 276 } 277 } 278 279 @Override setLaunchBounds(@ullable Rect screenSpacePixelRect)280 public @NonNull ActivityOptionsCompat setLaunchBounds(@Nullable Rect screenSpacePixelRect) { 281 if (Build.VERSION.SDK_INT < 24) { 282 return this; 283 } 284 return new ActivityOptionsCompatImpl( 285 mActivityOptions.setLaunchBounds(screenSpacePixelRect)); 286 } 287 288 @Override getLaunchBounds()289 public Rect getLaunchBounds() { 290 if (Build.VERSION.SDK_INT < 24) { 291 return null; 292 } 293 return mActivityOptions.getLaunchBounds(); 294 } 295 296 @Override setShareIdentityEnabled(boolean shareIdentity)297 public @NonNull ActivityOptionsCompat setShareIdentityEnabled(boolean shareIdentity) { 298 if (Build.VERSION.SDK_INT < 34) { 299 return this; 300 } 301 return new ActivityOptionsCompatImpl( 302 mActivityOptions.setShareIdentityEnabled(shareIdentity)); 303 } 304 305 @SuppressLint("WrongConstant") 306 @Override setPendingIntentBackgroundActivityStartMode( @ackgroundActivityStartMode int state)307 public @NonNull ActivityOptionsCompat setPendingIntentBackgroundActivityStartMode( 308 @BackgroundActivityStartMode int state) { 309 if (Build.VERSION.SDK_INT >= 34) { 310 mActivityOptions.setPendingIntentBackgroundActivityStartMode(state); 311 } else if (Build.VERSION.SDK_INT >= 33) { 312 // Matches the behavior of isPendingIntentBackgroundActivityLaunchAllowed(). 313 boolean isAllowed = state != ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED; 314 mActivityOptions.setPendingIntentBackgroundActivityLaunchAllowed(isAllowed); 315 } 316 return this; 317 } 318 319 @Override getLaunchDisplayId()320 public int getLaunchDisplayId() { 321 if (Build.VERSION.SDK_INT >= 26) { 322 return mActivityOptions.getLaunchDisplayId(); 323 } else { 324 return Display.INVALID_DISPLAY; 325 } 326 } 327 328 @Override setLaunchDisplayId(int launchDisplayId)329 public @NonNull ActivityOptionsCompat setLaunchDisplayId(int launchDisplayId) { 330 if (Build.VERSION.SDK_INT >= 26) { 331 mActivityOptions.setLaunchDisplayId(launchDisplayId); 332 } 333 return this; 334 } 335 } 336 ActivityOptionsCompat()337 protected ActivityOptionsCompat() { 338 } 339 340 /** 341 * Sets the bounds (window size) that the activity should be launched in. 342 * Rect position should be provided in pixels and in screen coordinates. 343 * Set to null explicitly for fullscreen. 344 * <p> 345 * <strong>NOTE:<strong/> This value is ignored on devices that don't have 346 * {@link android.content.pm.PackageManager#FEATURE_FREEFORM_WINDOW_MANAGEMENT} or 347 * {@link android.content.pm.PackageManager#FEATURE_PICTURE_IN_PICTURE} enabled. 348 * @param screenSpacePixelRect Launch bounds to use for the activity or null for fullscreen. 349 */ setLaunchBounds(@ullable Rect screenSpacePixelRect)350 public @NonNull ActivityOptionsCompat setLaunchBounds(@Nullable Rect screenSpacePixelRect) { 351 return this; 352 } 353 354 /** 355 * Returns the bounds that should be used to launch the activity. 356 * @see #setLaunchBounds(Rect) 357 * @return Bounds used to launch the activity. 358 */ getLaunchBounds()359 public @Nullable Rect getLaunchBounds() { 360 return null; 361 } 362 363 /** 364 * Returns the created options as a Bundle, which can be passed to 365 * {@link androidx.core.content.ContextCompat#startActivity(Context, android.content.Intent, Bundle)}. 366 * Note that the returned Bundle is still owned by the ActivityOptions 367 * object; you must not modify it, but can supply it to the startActivity 368 * methods that take an options Bundle. 369 */ toBundle()370 public @Nullable Bundle toBundle() { 371 return null; 372 } 373 374 /** 375 * Update the current values in this ActivityOptions from those supplied in 376 * otherOptions. Any values defined in otherOptions replace those in the 377 * base options. 378 */ update(@onNull ActivityOptionsCompat otherOptions)379 public void update(@NonNull ActivityOptionsCompat otherOptions) { 380 // Do nothing. 381 } 382 383 /** 384 * Ask the the system track that time the user spends in the app being launched, and 385 * report it back once done. The report will be sent to the given receiver, with 386 * the extras {@link #EXTRA_USAGE_TIME_REPORT} and {@link #EXTRA_USAGE_TIME_REPORT_PACKAGES} 387 * filled in. 388 * 389 * <p>The time interval tracked is from launching this activity until the user leaves 390 * that activity's flow. They are considered to stay in the flow as long as 391 * new activities are being launched or returned to from the original flow, 392 * even if this crosses package or task boundaries. For example, if the originator 393 * starts an activity to view an image, and while there the user selects to share, 394 * which launches their email app in a new task, and they complete the share, the 395 * time during that entire operation will be included until they finally hit back from 396 * the original image viewer activity.</p> 397 * 398 * <p>The user is considered to complete a flow once they switch to another 399 * activity that is not part of the tracked flow. This may happen, for example, by 400 * using the notification shade, launcher, or recents to launch or switch to another 401 * app. Simply going in to these navigation elements does not break the flow (although 402 * the launcher and recents stops time tracking of the session); it is the act of 403 * going somewhere else that completes the tracking.</p> 404 * 405 * @param receiver A broadcast receiver that will receive the report. 406 */ requestUsageTimeReport(@onNull PendingIntent receiver)407 public void requestUsageTimeReport(@NonNull PendingIntent receiver) { 408 // Do nothing. 409 } 410 411 /** 412 * Sets whether the identity of the launching app should be shared with the activity. 413 * 414 * <p>Use this option when starting an activity that needs to know the identity of the 415 * launching app; with this set to {@code true}, the activity will have access to the launching 416 * app's package name and uid. 417 * 418 * <p>Defaults to {@code false} if not set. This is a no-op before U. 419 * 420 * <p>Note, even if the launching app does not explicitly enable sharing of its identity, if 421 * the activity is started with {@code Activity#startActivityForResult}, then {@link 422 * Activity#getCallingPackage()} will still return the launching app's package name to 423 * allow validation of the result's recipient. Also, an activity running within a package 424 * signed by the same key used to sign the platform (some system apps such as Settings will 425 * be signed with the platform's key) will have access to the launching app's identity. 426 * 427 * @param shareIdentity whether the launching app's identity should be shared with the activity 428 * @return {@code this} {@link ActivityOptions} instance. 429 * @see Activity#getLaunchedFromPackage() 430 * @see Activity#getLaunchedFromUid() 431 */ setShareIdentityEnabled(boolean shareIdentity)432 public @NonNull ActivityOptionsCompat setShareIdentityEnabled(boolean shareIdentity) { 433 return this; 434 } 435 436 /** 437 * Sets the mode for allowing or denying the senders privileges to start background activities 438 * to the PendingIntent. 439 * <p/> 440 * This is typically used in when executing {@link PendingIntent#send(Context, int, Intent)} or 441 * similar methods. A privileged sender of a PendingIntent should only grant 442 * {@link ActivityOptions#MODE_BACKGROUND_ACTIVITY_START_ALLOWED} if the PendingIntent is from a 443 * trusted source and/or executed on behalf the user. 444 */ setPendingIntentBackgroundActivityStartMode( @ackgroundActivityStartMode int state)445 public @NonNull ActivityOptionsCompat setPendingIntentBackgroundActivityStartMode( 446 @BackgroundActivityStartMode int state) { 447 return this; 448 } 449 450 /** 451 * Gets the id of the display where activity should be launched. 452 * <p> 453 * On API 25 and below, this method always returns {@link Display#INVALID_DISPLAY}. 454 * 455 * @return The id of the display where activity should be launched, 456 * {@link android.view.Display#INVALID_DISPLAY} if not set. 457 * @see #setLaunchDisplayId(int) 458 */ getLaunchDisplayId()459 public int getLaunchDisplayId() { 460 return Display.INVALID_DISPLAY; 461 } 462 463 /** 464 * Sets the id of the display where the activity should be launched. 465 * An app can launch activities on public displays or displays where the app already has 466 * activities. Otherwise, trying to launch on a private display or providing an invalid display 467 * id will result in an exception. 468 * <p> 469 * Setting launch display id will be ignored on devices that don't have 470 * {@link android.content.pm.PackageManager#FEATURE_ACTIVITIES_ON_SECONDARY_DISPLAYS}. 471 * <p> 472 * On API 25 and below, calling this method has no effect. 473 * 474 * @param launchDisplayId The id of the display where the activity should be launched. 475 * @return {@code this} {@link ActivityOptions} instance. 476 */ setLaunchDisplayId(int launchDisplayId)477 public @NonNull ActivityOptionsCompat setLaunchDisplayId(int launchDisplayId) { 478 return this; 479 } 480 } 481