1 /* 2 * Copyright (C) 2022 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 static android.app.PendingIntent.FLAG_IMMUTABLE; 20 import static android.app.PendingIntent.FLAG_MUTABLE; 21 22 import android.annotation.SuppressLint; 23 import android.app.PendingIntent; 24 import android.content.Context; 25 import android.content.Intent; 26 import android.os.Build.VERSION; 27 import android.os.Build.VERSION_CODES; 28 import android.os.Bundle; 29 import android.os.Handler; 30 31 import androidx.annotation.IntDef; 32 import androidx.annotation.RequiresApi; 33 import androidx.annotation.RestrictTo; 34 35 import org.jspecify.annotations.NonNull; 36 import org.jspecify.annotations.Nullable; 37 38 import java.io.Closeable; 39 import java.lang.annotation.Retention; 40 import java.lang.annotation.RetentionPolicy; 41 import java.util.concurrent.CountDownLatch; 42 43 /** Helper for accessing features in {@link PendingIntent}. */ 44 public final class PendingIntentCompat { 45 46 @IntDef( 47 flag = true, 48 value = { 49 PendingIntent.FLAG_ONE_SHOT, 50 PendingIntent.FLAG_NO_CREATE, 51 PendingIntent.FLAG_CANCEL_CURRENT, 52 PendingIntent.FLAG_UPDATE_CURRENT, 53 Intent.FILL_IN_ACTION, 54 Intent.FILL_IN_DATA, 55 Intent.FILL_IN_CATEGORIES, 56 Intent.FILL_IN_COMPONENT, 57 Intent.FILL_IN_PACKAGE, 58 Intent.FILL_IN_SOURCE_BOUNDS, 59 Intent.FILL_IN_SELECTOR, 60 Intent.FILL_IN_CLIP_DATA 61 }) 62 @Retention(RetentionPolicy.SOURCE) 63 @RestrictTo(RestrictTo.Scope.LIBRARY) 64 public @interface Flags {} 65 66 /** 67 * Retrieves a {@link PendingIntent} with mandatory mutability flag set on supported platform 68 * versions. The caller provides the flag as combination of all the other values except 69 * mutability flag. This method combines mutability flag when necessary. See {@link 70 * PendingIntent#getActivities(Context, int, Intent[], int, Bundle)}. 71 */ getActivities( @onNull Context context, int requestCode, @SuppressLint("ArrayReturn") Intent @NonNull [] intents, @Flags int flags, @Nullable Bundle options, boolean isMutable)72 public static @NonNull PendingIntent getActivities( 73 @NonNull Context context, 74 int requestCode, 75 @SuppressLint("ArrayReturn") Intent @NonNull [] intents, 76 @Flags int flags, 77 @Nullable Bundle options, 78 boolean isMutable) { 79 return PendingIntent.getActivities(context, requestCode, intents, 80 addMutabilityFlags(isMutable, flags), options); 81 } 82 83 /** 84 * Retrieves a {@link PendingIntent} with mandatory mutability flag set on supported platform 85 * versions. The caller provides the flag as combination of all the other values except 86 * mutability flag. This method combines mutability flag when necessary. See {@link 87 * PendingIntent#getActivities(Context, int, Intent[], int, Bundle)}. 88 */ getActivities( @onNull Context context, int requestCode, @SuppressLint("ArrayReturn") Intent @NonNull [] intents, @Flags int flags, boolean isMutable)89 public static @NonNull PendingIntent getActivities( 90 @NonNull Context context, 91 int requestCode, 92 @SuppressLint("ArrayReturn") Intent @NonNull [] intents, 93 @Flags int flags, 94 boolean isMutable) { 95 return PendingIntent.getActivities( 96 context, requestCode, intents, addMutabilityFlags(isMutable, flags)); 97 } 98 99 /** 100 * Retrieves a {@link PendingIntent} with mandatory mutability flag set on supported platform 101 * versions. The caller provides the flag as combination of all the other values except 102 * mutability flag. This method combines mutability flag when necessary. 103 * 104 * @return Returns an existing or new PendingIntent matching the given parameters. May return 105 * {@code null} only if {@link PendingIntent#FLAG_NO_CREATE} has been supplied. 106 * @see PendingIntent#getActivity(Context, int, Intent, int) 107 */ getActivity( @onNull Context context, int requestCode, @NonNull Intent intent, @Flags int flags, boolean isMutable)108 public static @Nullable PendingIntent getActivity( 109 @NonNull Context context, 110 int requestCode, 111 @NonNull Intent intent, 112 @Flags int flags, 113 boolean isMutable) { 114 return PendingIntent.getActivity( 115 context, requestCode, intent, addMutabilityFlags(isMutable, flags)); 116 } 117 118 /** 119 * Retrieves a {@link PendingIntent} with mandatory mutability flag set on supported platform 120 * versions. The caller provides the flag as combination of all the other values except 121 * mutability flag. This method combines mutability flag when necessary. 122 * 123 * @return Returns an existing or new PendingIntent matching the given parameters. May return 124 * {@code null} only if {@link PendingIntent#FLAG_NO_CREATE} has been supplied. 125 * @see PendingIntent#getActivity(Context, int, Intent, int, Bundle) 126 */ getActivity( @onNull Context context, int requestCode, @NonNull Intent intent, @Flags int flags, @Nullable Bundle options, boolean isMutable)127 public static @Nullable PendingIntent getActivity( 128 @NonNull Context context, 129 int requestCode, 130 @NonNull Intent intent, 131 @Flags int flags, 132 @Nullable Bundle options, 133 boolean isMutable) { 134 return PendingIntent.getActivity(context, requestCode, intent, 135 addMutabilityFlags(isMutable, flags), options); 136 } 137 138 /** 139 * Retrieves a {@link PendingIntent} with mandatory mutability flag set on supported platform 140 * versions. The caller provides the flag as combination of all the other values except 141 * mutability flag. This method combines mutability flag when necessary. 142 * 143 * @return Returns an existing or new PendingIntent matching the given parameters. May return 144 * {@code null} only if {@link PendingIntent#FLAG_NO_CREATE} has been supplied. 145 * @see PendingIntent#getBroadcast(Context, int, Intent, int) 146 */ getBroadcast( @onNull Context context, int requestCode, @NonNull Intent intent, @Flags int flags, boolean isMutable)147 public static @Nullable PendingIntent getBroadcast( 148 @NonNull Context context, 149 int requestCode, 150 @NonNull Intent intent, 151 @Flags int flags, 152 boolean isMutable) { 153 return PendingIntent.getBroadcast( 154 context, requestCode, intent, addMutabilityFlags(isMutable, flags)); 155 } 156 157 /** 158 * Retrieves a {@link PendingIntent} with mandatory mutability flag set on supported platform 159 * versions. The caller provides the flag as combination of all the other values except 160 * mutability flag. This method combines mutability flag when necessary. See {@link 161 * PendingIntent#getForegroundService(Context, int, Intent, int)} . 162 */ 163 @RequiresApi(26) getForegroundService( @onNull Context context, int requestCode, @NonNull Intent intent, @Flags int flags, boolean isMutable)164 public static @NonNull PendingIntent getForegroundService( 165 @NonNull Context context, 166 int requestCode, 167 @NonNull Intent intent, 168 @Flags int flags, 169 boolean isMutable) { 170 return Api26Impl.getForegroundService( 171 context, requestCode, intent, addMutabilityFlags(isMutable, flags)); 172 } 173 174 /** 175 * Retrieves a {@link PendingIntent} with mandatory mutability flag set on supported platform 176 * versions. The caller provides the flag as combination of all the other values except 177 * mutability flag. This method combines mutability flag when necessary. 178 * 179 * @return Returns an existing or new PendingIntent matching the given parameters. May return 180 * {@code null} only if {@link PendingIntent#FLAG_NO_CREATE} has been supplied. 181 * @see PendingIntent#getService(Context, int, Intent, int) 182 */ getService( @onNull Context context, int requestCode, @NonNull Intent intent, @Flags int flags, boolean isMutable)183 public static @Nullable PendingIntent getService( 184 @NonNull Context context, 185 int requestCode, 186 @NonNull Intent intent, 187 @Flags int flags, 188 boolean isMutable) { 189 return PendingIntent.getService( 190 context, requestCode, intent, addMutabilityFlags(isMutable, flags)); 191 } 192 193 /** 194 * {@link PendingIntent#send()} variants that support {@link PendingIntent.OnFinished} callbacks 195 * have a bug on many API levels that the callback may be invoked even if the PendingIntent was 196 * never sent (ie, such as if the PendingIntent was canceled, and the send() invocation threw a 197 * {@link PendingIntent.CanceledException}). Using this compatibility method fixes that bug and 198 * guarantees that {@link PendingIntent.OnFinished} callbacks will only be invoked if send() 199 * completed successfully. 200 * 201 * <p>See {@link PendingIntent#send(int, PendingIntent.OnFinished, Handler)}. 202 */ 203 @SuppressLint("LambdaLast") // compat shim so arguments should be in the same order send( @onNull PendingIntent pendingIntent, int code, PendingIntent.@Nullable OnFinished onFinished, @Nullable Handler handler)204 public static void send( 205 @NonNull PendingIntent pendingIntent, 206 int code, 207 PendingIntent.@Nullable OnFinished onFinished, 208 @Nullable Handler handler) throws PendingIntent.CanceledException { 209 try (GatedCallback gatedCallback = new GatedCallback(onFinished)) { 210 pendingIntent.send(code, gatedCallback.getCallback(), handler); 211 gatedCallback.complete(); 212 } 213 } 214 215 /** 216 * {@link PendingIntent#send()} variants that support {@link PendingIntent.OnFinished} callbacks 217 * have a bug on many API levels that the callback may be invoked even if the PendingIntent was 218 * never sent (ie, such as if the PendingIntent was canceled, and the send() invocation threw a 219 * {@link PendingIntent.CanceledException}). Using this compatibility method fixes that bug and 220 * guarantees that {@link PendingIntent.OnFinished} callbacks will only be invoked if send() 221 * completed successfully. 222 * 223 * <p>See {@link PendingIntent#send(Context, int, Intent, PendingIntent.OnFinished, Handler)}. 224 */ 225 @SuppressLint("LambdaLast") // compat shim so arguments must be in the same order send( @onNull PendingIntent pendingIntent, @SuppressLint("ContextFirst") @NonNull Context context, int code, @NonNull Intent intent, PendingIntent.@Nullable OnFinished onFinished, @Nullable Handler handler)226 public static void send( 227 @NonNull PendingIntent pendingIntent, 228 // compat shim so arguments must be in the same order 229 @SuppressLint("ContextFirst") @NonNull Context context, 230 int code, 231 @NonNull Intent intent, 232 PendingIntent.@Nullable OnFinished onFinished, 233 @Nullable Handler handler) throws PendingIntent.CanceledException { 234 send(pendingIntent, context, code, intent, onFinished, handler, null, null); 235 } 236 237 /** 238 * {@link PendingIntent#send()} variants that support {@link PendingIntent.OnFinished} callbacks 239 * have a bug on many API levels that the callback may be invoked even if the PendingIntent was 240 * never sent (ie, such as if the PendingIntent was canceled, and the send() invocation threw a 241 * {@link PendingIntent.CanceledException}). Using this compatibility method fixes that bug and 242 * guarantees that {@link PendingIntent.OnFinished} callbacks will only be invoked if send() 243 * completed successfully. 244 * 245 * <p>See {@link 246 * PendingIntent#send(Context, int, Intent, PendingIntent.OnFinished, Handler, String, Bundle)} 247 */ 248 @SuppressLint("LambdaLast") // compat shim so arguments must be in the same order send( @onNull PendingIntent pendingIntent, @SuppressLint("ContextFirst") @NonNull Context context, int code, @NonNull Intent intent, PendingIntent.@Nullable OnFinished onFinished, @Nullable Handler handler, @Nullable String requiredPermissions, @Nullable Bundle options)249 public static void send( 250 @NonNull PendingIntent pendingIntent, 251 // compat shim so arguments must be in the same order 252 @SuppressLint("ContextFirst") @NonNull Context context, 253 int code, 254 @NonNull Intent intent, 255 PendingIntent.@Nullable OnFinished onFinished, 256 @Nullable Handler handler, 257 @Nullable String requiredPermissions, 258 @Nullable Bundle options) throws PendingIntent.CanceledException { 259 try (GatedCallback gatedCallback = new GatedCallback(onFinished)) { 260 if (VERSION.SDK_INT >= VERSION_CODES.M) { 261 Api23Impl.send( 262 pendingIntent, 263 context, 264 code, 265 intent, 266 onFinished, 267 handler, 268 requiredPermissions, 269 options); 270 } else { 271 pendingIntent.send(context, code, intent, gatedCallback.getCallback(), handler, 272 requiredPermissions); 273 } 274 gatedCallback.complete(); 275 } 276 } 277 addMutabilityFlags(boolean isMutable, int flags)278 static int addMutabilityFlags(boolean isMutable, int flags) { 279 if (isMutable) { 280 if (VERSION.SDK_INT >= 31) { 281 flags |= FLAG_MUTABLE; 282 } 283 } else { 284 if (VERSION.SDK_INT >= 23) { 285 flags |= FLAG_IMMUTABLE; 286 } 287 } 288 289 return flags; 290 } 291 PendingIntentCompat()292 private PendingIntentCompat() {} 293 294 @RequiresApi(23) 295 private static class Api23Impl { Api23Impl()296 private Api23Impl() {} 297 send( @onNull PendingIntent pendingIntent, @NonNull Context context, int code, @NonNull Intent intent, PendingIntent.@Nullable OnFinished onFinished, @Nullable Handler handler, @Nullable String requiredPermission, @Nullable Bundle options)298 public static void send( 299 @NonNull PendingIntent pendingIntent, 300 @NonNull Context context, 301 int code, 302 @NonNull Intent intent, 303 PendingIntent.@Nullable OnFinished onFinished, 304 @Nullable Handler handler, 305 @Nullable String requiredPermission, 306 @Nullable Bundle options) throws PendingIntent.CanceledException { 307 pendingIntent.send( 308 context, 309 code, 310 intent, 311 onFinished, 312 handler, 313 requiredPermission, 314 options); 315 } 316 } 317 318 @RequiresApi(26) 319 private static class Api26Impl { Api26Impl()320 private Api26Impl() {} 321 getForegroundService( Context context, int requestCode, Intent intent, int flags)322 public static PendingIntent getForegroundService( 323 Context context, int requestCode, Intent intent, int flags) { 324 return PendingIntent.getForegroundService(context, requestCode, intent, flags); 325 } 326 } 327 328 // see b/201299281 for more info and context 329 private static class GatedCallback implements Closeable { 330 331 private final CountDownLatch mComplete = new CountDownLatch(1); 332 333 private PendingIntent.@Nullable OnFinished mCallback; 334 private boolean mSuccess; 335 GatedCallback(PendingIntent.@ullable OnFinished callback)336 GatedCallback(PendingIntent.@Nullable OnFinished callback) { 337 this.mCallback = callback; 338 mSuccess = false; 339 } 340 getCallback()341 public PendingIntent.@Nullable OnFinished getCallback() { 342 if (mCallback == null) { 343 return null; 344 } else { 345 return this::onSendFinished; 346 } 347 } 348 complete()349 public void complete() { 350 mSuccess = true; 351 } 352 353 @Override close()354 public void close() { 355 if (!mSuccess) { 356 mCallback = null; 357 } 358 mComplete.countDown(); 359 } 360 onSendFinished( PendingIntent pendingIntent, Intent intent, int resultCode, String resultData, Bundle resultExtras)361 private void onSendFinished( 362 PendingIntent pendingIntent, 363 Intent intent, 364 int resultCode, 365 String resultData, 366 Bundle resultExtras) { 367 boolean interrupted = false; 368 try { 369 while (true) { 370 try { 371 mComplete.await(); 372 break; 373 } catch (InterruptedException e) { 374 interrupted = true; 375 } 376 } 377 } finally { 378 if (interrupted) { 379 Thread.currentThread().interrupt(); 380 } 381 } 382 383 if (mCallback != null) { 384 mCallback.onSendFinished( 385 pendingIntent, 386 intent, 387 resultCode, 388 resultData, 389 resultExtras); 390 mCallback = null; 391 } 392 } 393 } 394 } 395