Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 0d1ba2e8 authored by Achim Thesmann's avatar Achim Thesmann
Browse files

Prepare to restrict PI sender BAL privileges.

This prepares for a behavior change for an app that
- sends a PendingIntent created by another app
- does not explicitly set a value for `isPendingIntentBackgroundActivityLaunchAllowed`

Details about this change:

- We had to separate the state for BGFGS and BAL in the boolean used to
  represent both, while doing that we incorporated the token too,
  creating a new object BSP.
- The BackgroundStartPrivileges class now wraps the originatingToken and
  explicitly states the granted permissions.
- This BackgroundStartPrivileges object is passed through and is used to
  determine the correct permissions. This can probably be extended to
  handle while-in-use permissions as a separate case later too.
- The ComponentOptions API adds a new getter for apps to check on the
  validity of the `isPendingIntentBackgroundActivityLaunchAllowed`
  property.

Test: atest ActivityStarterTests BackgroundActivityLaunchTest
Change-Id: I467812f316de36edd5b36483a67db04fbd620735
Bug: 244637991
parent 622f76b7
Loading
Loading
Loading
Loading
+7 −2
Original line number Diff line number Diff line
@@ -4635,8 +4635,9 @@ package android.app {
    method @Nullable public android.graphics.Rect getLaunchBounds();
    method public int getLaunchDisplayId();
    method public boolean getLockTaskMode();
    method public int getPendingIntentBackgroundActivityStartMode();
    method public int getSplashScreenStyle();
    method public boolean isPendingIntentBackgroundActivityLaunchAllowed();
    method @Deprecated public boolean isPendingIntentBackgroundActivityLaunchAllowed();
    method public boolean isShareIdentityEnabled();
    method public static android.app.ActivityOptions makeBasic();
    method public static android.app.ActivityOptions makeClipRevealAnimation(android.view.View, int, int, int, int);
@@ -4653,13 +4654,17 @@ package android.app {
    method public android.app.ActivityOptions setLaunchBounds(@Nullable android.graphics.Rect);
    method public android.app.ActivityOptions setLaunchDisplayId(int);
    method public android.app.ActivityOptions setLockTaskEnabled(boolean);
    method public void setPendingIntentBackgroundActivityLaunchAllowed(boolean);
    method @Deprecated public void setPendingIntentBackgroundActivityLaunchAllowed(boolean);
    method @NonNull public android.app.ActivityOptions setPendingIntentBackgroundActivityStartMode(int);
    method @NonNull public android.app.ActivityOptions setShareIdentityEnabled(boolean);
    method @NonNull public android.app.ActivityOptions setSplashScreenStyle(int);
    method public android.os.Bundle toBundle();
    method public void update(android.app.ActivityOptions);
    field public static final String EXTRA_USAGE_TIME_REPORT = "android.activity.usage_time";
    field public static final String EXTRA_USAGE_TIME_REPORT_PACKAGES = "android.usage_time_packages";
    field public static final int MODE_BACKGROUND_ACTIVITY_START_ALLOWED = 1; // 0x1
    field public static final int MODE_BACKGROUND_ACTIVITY_START_DENIED = 2; // 0x2
    field public static final int MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED = 0; // 0x0
  }
  public class AlarmManager {
+4 −2
Original line number Diff line number Diff line
@@ -806,8 +806,9 @@ package android.app {
    method @Nullable public android.content.IntentFilter getDeliveryGroupMatchingFilter();
    method @Nullable public String getDeliveryGroupMatchingKey();
    method public int getDeliveryGroupPolicy();
    method public int getPendingIntentBackgroundActivityStartMode();
    method public boolean isDeferUntilActive();
    method public boolean isPendingIntentBackgroundActivityLaunchAllowed();
    method @Deprecated public boolean isPendingIntentBackgroundActivityLaunchAllowed();
    method public static android.app.BroadcastOptions makeBasic();
    method @RequiresPermission(android.Manifest.permission.ACCESS_BROADCAST_RESPONSE_STATS) public void recordResponseEventWhileInBackground(@IntRange(from=0) long);
    method @RequiresPermission(android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND) public void setBackgroundActivityStartsAllowed(boolean);
@@ -816,7 +817,8 @@ package android.app {
    method @NonNull public android.app.BroadcastOptions setDeliveryGroupMatchingKey(@NonNull String, @NonNull String);
    method @NonNull public android.app.BroadcastOptions setDeliveryGroupPolicy(int);
    method public void setDontSendToRestrictedApps(boolean);
    method public void setPendingIntentBackgroundActivityLaunchAllowed(boolean);
    method @Deprecated public void setPendingIntentBackgroundActivityLaunchAllowed(boolean);
    method @NonNull public android.app.BroadcastOptions setPendingIntentBackgroundActivityStartMode(int);
    method public void setRequireAllOfPermissions(@Nullable String[]);
    method public void setRequireCompatChange(long, boolean);
    method public void setRequireNoneOfPermissions(@Nullable String[]);
+3 −3
Original line number Diff line number Diff line
@@ -440,14 +440,14 @@ public abstract class ActivityManagerInternal {
            IApplicationThread resultToThread, IIntentReceiver resultTo, int resultCode,
            String resultData, Bundle resultExtras, String requiredPermission, Bundle bOptions,
            boolean serialized, boolean sticky, @UserIdInt int userId,
            boolean allowBackgroundActivityStarts, @Nullable IBinder backgroundActivityStartsToken,
            BackgroundStartPrivileges backgroundStartPrivileges,
            @Nullable int[] broadcastAllowList);

    public abstract ComponentName startServiceInPackage(int uid, Intent service,
            String resolvedType, boolean fgRequired, String callingPackage,
            @Nullable String callingFeatureId, @UserIdInt int userId,
            boolean allowBackgroundActivityStarts,
            @Nullable IBinder backgroundActivityStartsToken) throws TransactionTooLargeException;
            BackgroundStartPrivileges backgroundStartPrivileges)
            throws TransactionTooLargeException;

    public abstract void disconnectActivityFromServices(Object connectionHolder);
    public abstract void cleanUpServices(@UserIdInt int userId, ComponentName component,
+26 −0
Original line number Diff line number Diff line
@@ -2402,6 +2402,32 @@ public class ActivityOptions extends ComponentOptions {

    }

    /**
     * Sets the mode for allowing or denying the senders privileges to start background activities
     * to the PendingIntent.
     *
     * This is typically used in when executing {@link PendingIntent#send(Context, int, Intent,
     * PendingIntent.OnFinished, Handler, String, Bundle)} or similar
     * methods. A privileged sender of a PendingIntent should only grant
     * {@link #MODE_BACKGROUND_ACTIVITY_START_ALLOWED} if the PendingIntent is from a trusted source
     * and/or executed on behalf the user.
     */
    public @NonNull ActivityOptions setPendingIntentBackgroundActivityStartMode(
            @BackgroundActivityStartMode int state) {
        super.setPendingIntentBackgroundActivityStartMode(state);
        return this;
    }

    /**
     * Get the mode for allowing or denying the senders privileges to start background activities
     * to the PendingIntent.
     *
     * @see #setPendingIntentBackgroundActivityStartMode(int)
     */
    public @BackgroundActivityStartMode int getPendingIntentBackgroundActivityStartMode() {
        return super.getPendingIntentBackgroundActivityStartMode();
    }

    /** @hide */
    @Override
    public String toString() {
+184 −0
Original line number Diff line number Diff line
/*
 * 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 android.app;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.IBinder;

import com.android.internal.util.Preconditions;

import java.util.List;

/**
 * Privileges granted to a Process that allows it to execute starts from the background.
 * @hide
 */
public class BackgroundStartPrivileges {
    /** No privileges. */
    public static final BackgroundStartPrivileges NONE = new BackgroundStartPrivileges(
            false, false, null);
    /** Allow activity starts (and implies allowing foreground service starts).  */
    public static final BackgroundStartPrivileges ALLOW_BAL = new BackgroundStartPrivileges(
            true, true, null);
    /** Allow foreground service starts. */
    public static final BackgroundStartPrivileges ALLOW_FGS = new BackgroundStartPrivileges(
            false, true, null);

    private final boolean mAllowsBackgroundActivityStarts;
    private final boolean mAllowsBackgroundForegroundServiceStarts;
    private final IBinder mOriginatingToken;

    private BackgroundStartPrivileges(boolean allowsBackgroundActivityStarts,
            boolean allowsBackgroundForegroundServiceStarts, @Nullable IBinder originatingToken) {
        Preconditions.checkArgument(
                !allowsBackgroundActivityStarts || allowsBackgroundForegroundServiceStarts,
                "backgroundActivityStarts implies bgFgServiceStarts");
        mAllowsBackgroundActivityStarts = allowsBackgroundActivityStarts;
        mAllowsBackgroundForegroundServiceStarts = allowsBackgroundForegroundServiceStarts;
        mOriginatingToken = originatingToken;
    }

    /**
     * Return a token that allows background activity starts and attributes it to a specific
     * originatingToken.
     */
    public static BackgroundStartPrivileges allowBackgroundActivityStarts(
            @Nullable IBinder originatingToken) {
        if (originatingToken == null) {
            // try to avoid creating new instances
            return ALLOW_BAL;
        }
        return new BackgroundStartPrivileges(true, true, originatingToken);
    }

    /**
     * Merge this {@link BackgroundStartPrivileges} with another {@link BackgroundStartPrivileges}.
     *
     * The resulting object will grant the union of the privileges of the merged objects.
     * The originating tokens is retained only if both {@link BackgroundStartPrivileges} are the
     * same.
     *
     * If one of the merged objects is {@link #NONE} then the other object is returned and the
     * originating token is NOT cleared.
     */
    public @NonNull BackgroundStartPrivileges merge(@Nullable BackgroundStartPrivileges other) {
        // shortcuts in case
        if (other == NONE || other == null) {
            return this;
        }
        if (this == NONE) {
            return other;
        }

        boolean allowsBackgroundActivityStarts =
                this.allowsBackgroundActivityStarts() || other.allowsBackgroundActivityStarts();
        boolean allowsBackgroundFgsStarts =
                this.allowsBackgroundFgsStarts() || other.allowsBackgroundFgsStarts();
        if (this.mOriginatingToken == other.mOriginatingToken) {
            // can reuse this?
            if (this.mAllowsBackgroundActivityStarts == allowsBackgroundActivityStarts
                    && this.mAllowsBackgroundForegroundServiceStarts == allowsBackgroundFgsStarts) {
                return this;
            }
            // can reuse other?
            if (other.mAllowsBackgroundActivityStarts == allowsBackgroundActivityStarts
                   && other.mAllowsBackgroundForegroundServiceStarts == allowsBackgroundFgsStarts) {
                return other;
            }
            // need to create a new instance (this should never happen)
            return new BackgroundStartPrivileges(allowsBackgroundActivityStarts,
                    allowsBackgroundFgsStarts, this.mOriginatingToken);
        } else {
            // no originating token -> can use standard instance
            if (allowsBackgroundActivityStarts) {
                return ALLOW_BAL;
            } else if (allowsBackgroundFgsStarts) {
                return ALLOW_FGS;
            } else {
                return NONE;
            }
        }
    }

    /**
     * Merge a collection of {@link BackgroundStartPrivileges} into a single token.
     *
     * The resulting object will grant the union of the privileges of the merged objects.
     * The originating tokens is retained only if all {@link BackgroundStartPrivileges} are the
     * same.
     *
     * If the list contains {@link #NONE}s these are ignored.
     */
    public static @NonNull BackgroundStartPrivileges merge(
            @Nullable List<BackgroundStartPrivileges> list) {
        if (list == null || list.isEmpty()) {
            return NONE;
        }
        BackgroundStartPrivileges current = list.get(0);
        for (int i = list.size(); i-- > 1; ) {
            current = current.merge(list.get(i));
        }
        return current;
    }

    /**
     * @return {@code true} if this grants the permission to start background activities from the
     * background.
     */
    public boolean allowsBackgroundActivityStarts() {
        return mAllowsBackgroundActivityStarts;
    }

    /**
     * @return {@code true} this grants the permission to start foreground services from the
     * background. */
    public boolean allowsBackgroundFgsStarts() {
        return mAllowsBackgroundForegroundServiceStarts;
    }

    /** @return true if this grants any privileges. */
    public boolean allowsAny() {
        return mAllowsBackgroundActivityStarts || mAllowsBackgroundForegroundServiceStarts;
    }

    /** Return true if this grants no privileges. */
    public boolean allowsNothing() {
        return !allowsAny();
    }

    /**
     * Gets the originating token.
     *
     * The originating token is optional information that allows to trace back the origin of this
     * object. Besides debugging, this is used to e.g. identify privileges created by the
     * notification service.
     */
    public @Nullable IBinder getOriginatingToken() {
        return mOriginatingToken;
    }

    @Override
    public String toString() {
        return "BackgroundStartPrivileges["
                + "allowsBackgroundActivityStarts=" + mAllowsBackgroundActivityStarts
                + ", allowsBackgroundForegroundServiceStarts="
                + mAllowsBackgroundForegroundServiceStarts
                + ", originatingToken=" + mOriginatingToken
                + ']';
    }
}
Loading