1 /* 2 * Copyright (C) 2023 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.android.safetycenter.pendingintents; 18 19 import static android.Manifest.permission.START_TASKS_FROM_RECENTS; 20 import static android.os.Build.VERSION_CODES.TIRAMISU; 21 import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE; 22 23 import android.app.ActivityOptions; 24 import android.app.PendingIntent; 25 import android.util.Log; 26 27 import androidx.annotation.Nullable; 28 import androidx.annotation.RequiresApi; 29 import androidx.annotation.RequiresPermission; 30 31 import com.android.modules.utils.build.SdkLevel; 32 33 /** A class to facilitate sending {@link PendingIntent}s associated with Safety Center. */ 34 @RequiresApi(TIRAMISU) 35 public final class PendingIntentSender { 36 PendingIntentSender()37 private PendingIntentSender() {} 38 39 private static final String TAG = "PendingIntentSender"; 40 41 /** 42 * Sends a {@link PendingIntent}. 43 * 44 * <p>On U+, launching activities in the background is opt-in by using {@link 45 * ActivityOptions#MODE_BACKGROUND_ACTIVITY_START_ALLOWED}. This call opts-in this behavior as 46 * the Safety Center intents come from trusted sources and are used for navigation purposes. 47 */ send(PendingIntent pendingIntent)48 public static void send(PendingIntent pendingIntent) throws PendingIntent.CanceledException { 49 send(pendingIntent, createActivityOptions(pendingIntent, /* launchTaskId= */ null)); 50 } 51 52 /** Same as {@link #send(PendingIntent)} but with the given optional {@code launchTaskId}. */ 53 @RequiresPermission(START_TASKS_FROM_RECENTS) send(PendingIntent pendingIntent, @Nullable Integer launchTaskId)54 public static void send(PendingIntent pendingIntent, @Nullable Integer launchTaskId) 55 throws PendingIntent.CanceledException { 56 send(pendingIntent, createActivityOptions(pendingIntent, launchTaskId)); 57 } 58 send(PendingIntent pendingIntent, @Nullable ActivityOptions activityOptions)59 private static void send(PendingIntent pendingIntent, @Nullable ActivityOptions activityOptions) 60 throws PendingIntent.CanceledException { 61 if (activityOptions == null) { 62 pendingIntent.send(); 63 return; 64 } 65 pendingIntent.send( 66 /* context= */ null, 67 /* code= */ 0, 68 /* intent= */ null, 69 /* onFinished= */ null, 70 /* handler= */ null, 71 /* requiredPermission= */ null, 72 activityOptions.toBundle()); 73 } 74 75 /** 76 * Same as {@link #send(PendingIntent)} but returns whether the call was successful instead of 77 * throwing a {@link PendingIntent#CanceledException}. 78 */ trySend(PendingIntent pendingIntent)79 public static boolean trySend(PendingIntent pendingIntent) { 80 return trySend( 81 pendingIntent, createActivityOptions(pendingIntent, /* launchTaskId= */ null)); 82 } 83 84 /** 85 * Same as {@link #send(PendingIntent, Integer)} but returns whether the call was successful 86 * instead of throwing a {@link PendingIntent#CanceledException}. 87 */ 88 @RequiresPermission(START_TASKS_FROM_RECENTS) trySend(PendingIntent pendingIntent, @Nullable Integer launchTaskId)89 public static boolean trySend(PendingIntent pendingIntent, @Nullable Integer launchTaskId) { 90 return trySend(pendingIntent, createActivityOptions(pendingIntent, launchTaskId)); 91 } 92 trySend( PendingIntent pendingIntent, @Nullable ActivityOptions activityOptions)93 private static boolean trySend( 94 PendingIntent pendingIntent, @Nullable ActivityOptions activityOptions) { 95 try { 96 send(pendingIntent, activityOptions); 97 return true; 98 } catch (PendingIntent.CanceledException ex) { 99 Log.e( 100 TAG, 101 "Couldn't send PendingIntent: " 102 + pendingIntent 103 + " with ActivityOptions:" 104 + activityOptions, 105 ex); 106 return false; 107 } 108 } 109 110 @Nullable createActivityOptions( PendingIntent pendingIntent, @Nullable Integer launchTaskId)111 private static ActivityOptions createActivityOptions( 112 PendingIntent pendingIntent, @Nullable Integer launchTaskId) { 113 if (!pendingIntent.isActivity()) { 114 return null; 115 } 116 if (launchTaskId == null && !SdkLevel.isAtLeastU()) { 117 return null; 118 } 119 ActivityOptions activityOptions = ActivityOptions.makeBasic(); 120 if (launchTaskId != null) { 121 activityOptions.setLaunchTaskId(launchTaskId); 122 } 123 if (SdkLevel.isAtLeastU()) { 124 setBackgroundActivityStartModeAllowed(activityOptions); 125 } 126 return activityOptions; 127 } 128 129 @RequiresApi(UPSIDE_DOWN_CAKE) setBackgroundActivityStartModeAllowed(ActivityOptions activityOptions)130 private static void setBackgroundActivityStartModeAllowed(ActivityOptions activityOptions) { 131 activityOptions.setPendingIntentBackgroundActivityStartMode( 132 ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED); 133 } 134 } 135