[go: nahoru, domu]

Merge "[GH] Implement PendingIntentCompat to enforce mutability flag." into androidx-main
diff --git a/core/core/api/current.txt b/core/core/api/current.txt
index 4919591..30803b3 100644
--- a/core/core/api/current.txt
+++ b/core/core/api/current.txt
@@ -800,6 +800,16 @@
     method public void removeOnPictureInPictureModeChangedListener(androidx.core.util.Consumer<androidx.core.app.PictureInPictureModeChangedInfo!>);
   }
 
+  public final class PendingIntentCompat {
+    method public static android.app.PendingIntent getActivities(android.content.Context, int, android.content.Intent![], int, android.os.Bundle, boolean);
+    method public static android.app.PendingIntent getActivities(android.content.Context, int, android.content.Intent![], int, boolean);
+    method public static android.app.PendingIntent getActivity(android.content.Context, int, android.content.Intent, int, boolean);
+    method public static android.app.PendingIntent getActivity(android.content.Context, int, android.content.Intent, int, android.os.Bundle, boolean);
+    method public static android.app.PendingIntent getBroadcast(android.content.Context, int, android.content.Intent, int, boolean);
+    method @RequiresApi(26) public static android.app.PendingIntent getForegroundService(android.content.Context, int, android.content.Intent, int, boolean);
+    method public static android.app.PendingIntent getService(android.content.Context, int, android.content.Intent, int, boolean);
+  }
+
   public class Person {
     method public static androidx.core.app.Person fromBundle(android.os.Bundle);
     method public androidx.core.graphics.drawable.IconCompat? getIcon();
diff --git a/core/core/api/public_plus_experimental_current.txt b/core/core/api/public_plus_experimental_current.txt
index e539396..7aa1a18 100644
--- a/core/core/api/public_plus_experimental_current.txt
+++ b/core/core/api/public_plus_experimental_current.txt
@@ -800,6 +800,16 @@
     method public void removeOnPictureInPictureModeChangedListener(androidx.core.util.Consumer<androidx.core.app.PictureInPictureModeChangedInfo!>);
   }
 
+  public final class PendingIntentCompat {
+    method public static android.app.PendingIntent getActivities(android.content.Context, int, android.content.Intent![], int, android.os.Bundle, boolean);
+    method public static android.app.PendingIntent getActivities(android.content.Context, int, android.content.Intent![], int, boolean);
+    method public static android.app.PendingIntent getActivity(android.content.Context, int, android.content.Intent, int, boolean);
+    method public static android.app.PendingIntent getActivity(android.content.Context, int, android.content.Intent, int, android.os.Bundle, boolean);
+    method public static android.app.PendingIntent getBroadcast(android.content.Context, int, android.content.Intent, int, boolean);
+    method @RequiresApi(26) public static android.app.PendingIntent getForegroundService(android.content.Context, int, android.content.Intent, int, boolean);
+    method public static android.app.PendingIntent getService(android.content.Context, int, android.content.Intent, int, boolean);
+  }
+
   public class Person {
     method public static androidx.core.app.Person fromBundle(android.os.Bundle);
     method public androidx.core.graphics.drawable.IconCompat? getIcon();
diff --git a/core/core/api/restricted_current.txt b/core/core/api/restricted_current.txt
index 86dafe9..cc32575 100644
--- a/core/core/api/restricted_current.txt
+++ b/core/core/api/restricted_current.txt
@@ -888,6 +888,16 @@
     method public void removeOnPictureInPictureModeChangedListener(androidx.core.util.Consumer<androidx.core.app.PictureInPictureModeChangedInfo!>);
   }
 
+  public final class PendingIntentCompat {
+    method public static android.app.PendingIntent getActivities(android.content.Context, int, android.content.Intent![], int, android.os.Bundle, boolean);
+    method public static android.app.PendingIntent getActivities(android.content.Context, int, android.content.Intent![], int, boolean);
+    method public static android.app.PendingIntent getActivity(android.content.Context, int, android.content.Intent, int, boolean);
+    method public static android.app.PendingIntent getActivity(android.content.Context, int, android.content.Intent, int, android.os.Bundle, boolean);
+    method public static android.app.PendingIntent getBroadcast(android.content.Context, int, android.content.Intent, int, boolean);
+    method @RequiresApi(26) public static android.app.PendingIntent getForegroundService(android.content.Context, int, android.content.Intent, int, boolean);
+    method public static android.app.PendingIntent getService(android.content.Context, int, android.content.Intent, int, boolean);
+  }
+
   public class Person {
     method @RequiresApi(28) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static androidx.core.app.Person fromAndroidPerson(android.app.Person);
     method public static androidx.core.app.Person fromBundle(android.os.Bundle);
diff --git a/core/core/src/main/java/androidx/core/app/PendingIntentCompat.java b/core/core/src/main/java/androidx/core/app/PendingIntentCompat.java
new file mode 100644
index 0000000..db89748
--- /dev/null
+++ b/core/core/src/main/java/androidx/core/app/PendingIntentCompat.java
@@ -0,0 +1,234 @@
+/*

+ * Copyright (C) 2022 The Android Open Source Project

+ *

+ * Licensed under the Apache License, Version 2.0 (the "License");

+ * you may not use this file except in compliance with the License.

+ * You may obtain a copy of the License at

+ *

+ *      http://www.apache.org/licenses/LICENSE-2.0

+ *

+ * Unless required by applicable law or agreed to in writing, software

+ * distributed under the License is distributed on an "AS IS" BASIS,

+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

+ * See the License for the specific language governing permissions and

+ * limitations under the License.

+ */

+

+package androidx.core.app;

+

+import static android.app.PendingIntent.FLAG_IMMUTABLE;

+import static android.app.PendingIntent.FLAG_MUTABLE;

+

+import android.annotation.SuppressLint;

+import android.app.PendingIntent;

+import android.content.Context;

+import android.content.Intent;

+import android.os.Build;

+import android.os.Bundle;

+

+import androidx.annotation.DoNotInline;

+import androidx.annotation.IntDef;

+import androidx.annotation.NonNull;

+import androidx.annotation.RequiresApi;

+

+import java.lang.annotation.Retention;

+import java.lang.annotation.RetentionPolicy;

+

+/** Helper for accessing features in {@link PendingIntent}. */

+public final class PendingIntentCompat {

+

+    /** @hide */

+    @IntDef(

+            flag = true,

+            value = {

+                PendingIntent.FLAG_ONE_SHOT,

+                PendingIntent.FLAG_NO_CREATE,

+                PendingIntent.FLAG_CANCEL_CURRENT,

+                PendingIntent.FLAG_UPDATE_CURRENT,

+                Intent.FILL_IN_ACTION,

+                Intent.FILL_IN_DATA,

+                Intent.FILL_IN_CATEGORIES,

+                Intent.FILL_IN_COMPONENT,

+                Intent.FILL_IN_PACKAGE,

+                Intent.FILL_IN_SOURCE_BOUNDS,

+                Intent.FILL_IN_SELECTOR,

+                Intent.FILL_IN_CLIP_DATA

+            })

+    @Retention(RetentionPolicy.SOURCE)

+    public @interface Flags {}

+

+    /**

+     * Retrieves a {@link PendingIntent} with mandatory mutability flag set on supported platform

+     * versions. The caller provides the flag as combination of all the other values except

+     * mutability flag. This method combines mutability flag when necessary. See {@link

+     * PendingIntent#getActivities(Context, int, Intent[], int, Bundle)}.

+     */

+    public static @NonNull PendingIntent getActivities(

+            @NonNull Context context,

+            int requestCode,

+            @NonNull @SuppressLint("ArrayReturn") Intent[] intents,

+            @Flags int flags,

+            @NonNull Bundle options,

+            boolean isMutable) {

+        if (Build.VERSION.SDK_INT >= 16) {

+            return Api16Impl.getActivities(

+                    context, requestCode, intents, addMutabilityFlags(isMutable, flags), options);

+        } else {

+            return PendingIntent.getActivities(context, requestCode, intents, flags);

+        }

+    }

+

+    /**

+     * Retrieves a {@link PendingIntent} with mandatory mutability flag set on supported platform

+     * versions. The caller provides the flag as combination of all the other values except

+     * mutability flag. This method combines mutability flag when necessary. See {@link

+     * PendingIntent#getActivities(Context, int, Intent[], int, Bundle)}.

+     */

+    public static @NonNull PendingIntent getActivities(

+            @NonNull Context context,

+            int requestCode,

+            @NonNull @SuppressLint("ArrayReturn") Intent[] intents,

+            @Flags int flags,

+            boolean isMutable) {

+        return PendingIntent.getActivities(

+                context, requestCode, intents, addMutabilityFlags(isMutable, flags));

+    }

+

+    /**

+     * Retrieves a {@link PendingIntent} with mandatory mutability flag set on supported platform

+     * versions. The caller provides the flag as combination of all the other values except

+     * mutability flag. This method combines mutability flag when necessary. See {@link

+     * PendingIntent#getActivity(Context, int, Intent, int)}.

+     */

+    public static @NonNull PendingIntent getActivity(

+            @NonNull Context context,

+            int requestCode,

+            @NonNull Intent intent,

+            @Flags int flags,

+            boolean isMutable) {

+        return PendingIntent.getActivity(

+                context, requestCode, intent, addMutabilityFlags(isMutable, flags));

+    }

+

+    /**

+     * Retrieves a {@link PendingIntent} with mandatory mutability flag set on supported platform

+     * versions. The caller provides the flag as combination of all the other values except

+     * mutability flag. This method combines mutability flag when necessary. See {@link

+     * PendingIntent#getActivity(Context, int, Intent, int, Bundle)}.

+     */

+    public static @NonNull PendingIntent getActivity(

+            @NonNull Context context,

+            int requestCode,

+            @NonNull Intent intent,

+            @Flags int flags,

+            @NonNull Bundle options,

+            boolean isMutable) {

+        if (Build.VERSION.SDK_INT >= 16) {

+            return Api16Impl.getActivity(

+                    context, requestCode, intent, addMutabilityFlags(isMutable, flags), options);

+        } else {

+            return PendingIntent.getActivity(context, requestCode, intent, flags);

+        }

+    }

+

+    /**

+     * Retrieves a {@link PendingIntent} with mandatory mutability flag set on supported platform

+     * versions. The caller provides the flag as combination of all the other values except

+     * mutability flag. This method combines mutability flag when necessary. See {@link

+     * PendingIntent#getBroadcast(Context, int, Intent, int)}.

+     */

+    public static @NonNull PendingIntent getBroadcast(

+            @NonNull Context context,

+            int requestCode,

+            @NonNull Intent intent,

+            @Flags int flags,

+            boolean isMutable) {

+        return PendingIntent.getBroadcast(

+                context, requestCode, intent, addMutabilityFlags(isMutable, flags));

+    }

+

+    /**

+     * Retrieves a {@link PendingIntent} with mandatory mutability flag set on supported platform

+     * versions. The caller provides the flag as combination of all the other values except

+     * mutability flag. This method combines mutability flag when necessary. See {@link

+     * PendingIntent#getForegroundService(Context, int, Intent, int)} .

+     */

+    @RequiresApi(26)

+    public static @NonNull PendingIntent getForegroundService(

+            @NonNull Context context,

+            int requestCode,

+            @NonNull Intent intent,

+            @Flags int flags,

+            boolean isMutable) {

+        return Api26Impl.getForegroundService(

+                context, requestCode, intent, addMutabilityFlags(isMutable, flags));

+    }

+

+    /**

+     * Retrieves a {@link PendingIntent} with mandatory mutability flag set on supported platform

+     * versions. The caller provides the flag as combination of all the other values except

+     * mutability flag. This method combines mutability flag when necessary. See {@link

+     * PendingIntent#getService(Context, int, Intent, int)}.

+     */

+    public static @NonNull PendingIntent getService(

+            @NonNull Context context,

+            int requestCode,

+            @NonNull Intent intent,

+            @Flags int flags,

+            boolean isMutable) {

+        return PendingIntent.getService(

+                context, requestCode, intent, addMutabilityFlags(isMutable, flags));

+    }

+

+    private static int addMutabilityFlags(boolean isMutable, int flags) {

+        if (isMutable) {

+            if (Build.VERSION.SDK_INT >= 31) {

+                flags |= FLAG_MUTABLE;

+            }

+        } else {

+            if (Build.VERSION.SDK_INT >= 23) {

+                flags |= FLAG_IMMUTABLE;

+            }

+        }

+

+        return flags;

+    }

+

+    private PendingIntentCompat() {}

+

+    @RequiresApi(16)

+    private static class Api16Impl {

+        private Api16Impl() {}

+

+        @DoNotInline

+        public static @NonNull PendingIntent getActivities(

+                @NonNull Context context,

+                int requestCode,

+                @NonNull @SuppressLint("ArrayReturn") Intent[] intents,

+                @Flags int flags,

+                @NonNull Bundle options) {

+            return PendingIntent.getActivities(context, requestCode, intents, flags, options);

+        }

+

+        @DoNotInline

+        public static @NonNull PendingIntent getActivity(

+                @NonNull Context context,

+                int requestCode,

+                @NonNull Intent intent,

+                @Flags int flags,

+                @NonNull Bundle options) {

+            return PendingIntent.getActivity(context, requestCode, intent, flags, options);

+        }

+    }

+

+    @RequiresApi(26)

+    private static class Api26Impl {

+        private Api26Impl() {}

+

+        @DoNotInline

+        public static PendingIntent getForegroundService(

+                Context context, int requestCode, Intent intent, int flags) {

+            return PendingIntent.getForegroundService(context, requestCode, intent, flags);

+        }

+    }

+}

diff --git a/core/core/src/test/java/androidx/core/app/PendingIntentCompatTest.java b/core/core/src/test/java/androidx/core/app/PendingIntentCompatTest.java
new file mode 100644
index 0000000..22eed6b
--- /dev/null
+++ b/core/core/src/test/java/androidx/core/app/PendingIntentCompatTest.java
@@ -0,0 +1,238 @@
+/*

+ * Copyright 2022 The Android Open Source Project

+ *

+ * Licensed under the Apache License, Version 2.0 (the "License");

+ * you may not use this file except in compliance with the License.

+ * You may obtain a copy of the License at

+ *

+ *      http://www.apache.org/licenses/LICENSE-2.0

+ *

+ * Unless required by applicable law or agreed to in writing, software

+ * distributed under the License is distributed on an "AS IS" BASIS,

+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

+ * See the License for the specific language governing permissions and

+ * limitations under the License.

+ */

+package androidx.core.app;

+

+import static com.google.common.truth.Truth.assertThat;

+

+import static org.robolectric.Shadows.shadowOf;

+

+import android.annotation.TargetApi;

+import android.app.PendingIntent;

+import android.content.Context;

+import android.content.Intent;

+import android.os.Bundle;

+

+import androidx.test.core.app.ApplicationProvider;

+

+import org.junit.Test;

+import org.junit.runner.RunWith;

+import org.robolectric.RobolectricTestRunner;

+import org.robolectric.annotation.Config;

+import org.robolectric.shadows.ShadowPendingIntent;

+

+/** Unit test for {@link PendingIntentCompat}. */

+@RunWith(RobolectricTestRunner.class)

+public class PendingIntentCompatTest {

+    private final Context context = ApplicationProvider.getApplicationContext();

+

+    @Config(maxSdk = 22)

+    @Test

+    public void addMutabilityFlags_immutableOnPreM() {

+        int requestCode = 7465;

+        Intent intent = new Intent();

+        Bundle options = new Bundle();

+        ShadowPendingIntent shadow =

+                shadowOf(

+                        PendingIntentCompat.getActivity(

+                                context,

+                                requestCode,

+                                intent,

+                                PendingIntent.FLAG_UPDATE_CURRENT,

+                                options,

+                                /* isMutable= */ false));

+        assertThat(shadow.isActivityIntent()).isTrue();

+        assertThat(shadow.getFlags()).isEqualTo(PendingIntent.FLAG_UPDATE_CURRENT);

+        assertThat(shadow.getRequestCode()).isEqualTo(requestCode);

+    }

+

+    @Config(minSdk = 23)

+    @Test

+    public void addMutabilityFlags_immutableOnMPlus() {

+        int requestCode = 7465;

+        Intent intent = new Intent();

+        Bundle options = new Bundle();

+        ShadowPendingIntent shadow =

+                shadowOf(

+                        PendingIntentCompat.getActivity(

+                                context,

+                                requestCode,

+                                intent,

+                                PendingIntent.FLAG_UPDATE_CURRENT,

+                                options,

+                                /* isMutable= */ false));

+        assertThat(shadow.isActivityIntent()).isTrue();

+        assertThat(shadow.getFlags())

+                .isEqualTo(PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);

+        assertThat(shadow.getRequestCode()).isEqualTo(requestCode);

+    }

+

+    @Config(maxSdk = 30)

+    @Test

+    public void addMutabilityFlags_mutableOnPreS() {

+        int requestCode = 7465;

+        Intent intent = new Intent();

+        Bundle options = new Bundle();

+        ShadowPendingIntent shadow =

+                shadowOf(

+                        PendingIntentCompat.getActivity(

+                                context,

+                                requestCode,

+                                intent,

+                                PendingIntent.FLAG_UPDATE_CURRENT,

+                                options,

+                                /* isMutable= */ true));

+        assertThat(shadow.isActivityIntent()).isTrue();

+        assertThat(shadow.getFlags()).isEqualTo(PendingIntent.FLAG_UPDATE_CURRENT);

+        assertThat(shadow.getRequestCode()).isEqualTo(requestCode);

+    }

+

+    @Config(minSdk = 31)

+    @Test

+    public void addMutabilityFlags_mutableOnSPlus() {

+        int requestCode = 7465;

+        Intent intent = new Intent();

+        Bundle options = new Bundle();

+        ShadowPendingIntent shadow =

+                shadowOf(

+                        PendingIntentCompat.getActivity(

+                                context,

+                                requestCode,

+                                intent,

+                                PendingIntent.FLAG_UPDATE_CURRENT,

+                                options,

+                                /* isMutable= */ true));

+        assertThat(shadow.isActivityIntent()).isTrue();

+        assertThat(shadow.getFlags())

+                .isEqualTo(PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE);

+        assertThat(shadow.getRequestCode()).isEqualTo(requestCode);

+    }

+

+    @Test

+    public void getActivities_withBundle() {

+        int requestCode = 7465;

+        Intent[] intents = new Intent[] {};

+        Bundle options = new Bundle();

+        ShadowPendingIntent shadow =

+                shadowOf(

+                        PendingIntentCompat.getActivities(

+                                context,

+                                requestCode,

+                                intents,

+                                PendingIntent.FLAG_UPDATE_CURRENT,

+                                options,

+                                /* isMutable= */ false));

+        assertThat(shadow.isActivityIntent()).isTrue();

+    }

+

+    @Test

+    public void getActivities() {

+        int requestCode = 7465;

+        Intent[] intents = new Intent[] {};

+        Bundle options = new Bundle();

+        ShadowPendingIntent shadow =

+                shadowOf(

+                        PendingIntentCompat.getActivities(

+                                context,

+                                requestCode,

+                                intents,

+                                PendingIntent.FLAG_UPDATE_CURRENT,

+                                /* isMutable= */ false));

+        assertThat(shadow.isActivityIntent()).isTrue();

+    }

+

+    @Test

+    public void getActivity_withBundle() {

+        int requestCode = 7465;

+        Intent intent = new Intent();

+        Bundle options = new Bundle();

+        ShadowPendingIntent shadow =

+                shadowOf(

+                        PendingIntentCompat.getActivity(

+                                context,

+                                requestCode,

+                                intent,

+                                PendingIntent.FLAG_UPDATE_CURRENT,

+                                options,

+                                /* isMutable= */ false));

+        assertThat(shadow.isActivityIntent()).isTrue();

+    }

+

+    @Test

+    public void getActivity() {

+        int requestCode = 7465;

+        Intent intent = new Intent();

+        Bundle options = new Bundle();

+        ShadowPendingIntent shadow =

+                shadowOf(

+                        PendingIntentCompat.getActivity(

+                                context,

+                                requestCode,

+                                intent,

+                                PendingIntent.FLAG_UPDATE_CURRENT,

+                                /* isMutable= */ false));

+        assertThat(shadow.isActivityIntent()).isTrue();

+    }

+

+    @Test

+    public void getService() {

+        int requestCode = 7465;

+        Intent intent = new Intent();

+        Bundle options = new Bundle();

+        ShadowPendingIntent shadow =

+                shadowOf(

+                        PendingIntentCompat.getService(

+                                context,

+                                requestCode,

+                                intent,

+                                PendingIntent.FLAG_UPDATE_CURRENT,

+                                /* isMutable= */ false));

+        assertThat(shadow.isService()).isTrue();

+    }

+

+    @Test

+    public void getBroadcast() {

+        int requestCode = 7465;

+        Intent intent = new Intent();

+        Bundle options = new Bundle();

+        ShadowPendingIntent shadow =

+                shadowOf(

+                        PendingIntentCompat.getBroadcast(

+                                context,

+                                requestCode,

+                                intent,

+                                PendingIntent.FLAG_UPDATE_CURRENT,

+                                /* isMutable= */ false));

+        assertThat(shadow.isBroadcast()).isTrue();

+    }

+

+    @TargetApi(26)

+    @Config(minSdk = 26)

+    @Test

+    public void getForegroundService() {

+        int requestCode = 7465;

+        Intent intent = new Intent();

+        Bundle options = new Bundle();

+        ShadowPendingIntent shadow =

+                shadowOf(

+                        PendingIntentCompat.getForegroundService(

+                                context,

+                                requestCode,

+                                intent,

+                                PendingIntent.FLAG_UPDATE_CURRENT,

+                                /* isMutable= */ false));

+        assertThat(shadow.isForegroundService()).isTrue();

+    }

+}