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

Commit ed3f30f1 authored by Olivier Nshimiye's avatar Olivier Nshimiye
Browse files

Add an allowlist of private space apps to receive (LOCKED_)BOOT_COMPLETED

immediately

We added changes to delay all (LOCKED_)BOOT_COMPLETED broadcasts for
private space apps until the app starts in ag/26624610.

This cl adds the ability for some apps to receive the (LOCKED_)BOOT_COMPLETED
broadcast immediately after private profile unlock using an allowlist
defined in f/b/core/res/values/config.xml

Bug: 399071793
Test: Verified on device
Flag: android.multiuser.enable_moving_content_into_private_space

Change-Id: I17e3945c98b8298c0f7219332bc37023b9b6ff0d
parent f54fcb71
Loading
Loading
Loading
Loading
+7 −0
Original line number Diff line number Diff line
@@ -7510,4 +7510,11 @@

    <!-- The selection toolbar render service component name -->
    <string translatable="false" name="config_systemUiSelectionToolbarRenderService">com.android.systemui/.selectiontoolbar.app.service.SysUiSelectionToolbarRenderService</string>

    <!-- An allowlist of Private Space apps that should receive {@link android.intent.action.LOCKED_BOOT_COMPLETED}
     and {@link android.intent.action.BOOT_COMPLETED} intents immediately after private space gets
      unlocked instead of when these apps start. -->
    <string-array name="config_privateSpaceBootCompletedImmediateReceivers" translatable="false">
        <item>com.android.privatespace</item>
    </string-array>
</resources>
+3 −0
Original line number Diff line number Diff line
@@ -3394,6 +3394,9 @@
  <!-- Message shown in the dialog prompting the user to set up a screen lock to delete private space from Reset Options [CHAR LIMIT=120] -->
  <java-symbol type="string" name="private_space_set_up_screen_lock_for_reset" />

  <!-- Allowlist of private space apps to receive ACTION_(LOCKED_)BOOT_COMPLETED intents immediately -->
  <java-symbol type="array" name="config_privateSpaceBootCompletedImmediateReceivers" />

  <java-symbol type="string" name="deprecated_target_sdk_message" />
  <java-symbol type="string" name="deprecated_target_sdk_app_store" />

+27 −12
Original line number Diff line number Diff line
@@ -431,6 +431,7 @@ import com.android.internal.os.TransferPipe;
import com.android.internal.os.Zygote;
import com.android.internal.pm.pkg.parsing.ParsingPackageUtils;
import com.android.internal.policy.AttributeCache;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.FastPrintWriter;
import com.android.internal.util.FrameworkStatsLog;
@@ -5077,26 +5078,40 @@ public class ActivityManagerService extends IActivityManager.Stub
     */
    private void maybeSendBootCompletedLocked(ProcessRecord app, boolean isRestrictedBackupMode) {
        boolean sendBroadcast = false;
        if (android.os.Flags.allowPrivateProfile()
                && android.multiuser.Flags.enablePrivateSpaceFeatures()) {
        final UserManagerInternal umInternal =
                LocalServices.getService(UserManagerInternal.class);
        UserInfo userInfo = umInternal.getUserInfo(app.userId);
        String packageName = app.info.packageName;
        if (userInfo != null && userInfo.isPrivateProfile()) {
            // Packages in private space get deferred boot completed whenever they start the
            // first time since profile start
                if (!mPrivateSpaceBootCompletedPackages.contains(app.info.packageName)) {
                    mPrivateSpaceBootCompletedPackages.add(app.info.packageName);
            if (!mPrivateSpaceBootCompletedPackages.contains(packageName)) {
                // Skipping the apps that are allowlisted to receive LOCKED_BOOT_COMPLETED and
                // BOOT_COMPLETED immediately after the profile unlock
                if (android.multiuser.Flags.enableMovingContentIntoPrivateSpace()) {
                    String[] allowlistedPackages = mContext.getResources().getStringArray(
                            com.android.internal
                                    .R.array.config_privateSpaceBootCompletedImmediateReceivers);
                    if (!ArrayUtils.contains(allowlistedPackages, packageName)) {
                        mPrivateSpaceBootCompletedPackages.add(packageName);
                        sendBroadcast = true;
                    } else {
                        mPrivateSpaceBootCompletedPackages.addAll(List.of(allowlistedPackages));
                    }
                } else {
                    mPrivateSpaceBootCompletedPackages.add(packageName);
                    sendBroadcast = true;
                } // else, stopped packages in private space may still hit the logic below
                }
            }
            // else, stopped packages in private space may still hit the logic below
        }
        final boolean wasForceStopped = app.wasForceStopped()
                || app.getWindowProcessController().wasForceStopped();
        if (android.app.Flags.appRestrictionsApi() && wasForceStopped) {
            noteAppRestrictionEnabled(app.info.packageName, app.uid,
            noteAppRestrictionEnabled(packageName, app.uid,
                    RESTRICTION_LEVEL_FORCE_STOPPED, false,
                    RESTRICTION_REASON_USAGE, "unknown", RESTRICTION_SOURCE_USER, 0L);
        }
+95 −32
Original line number Diff line number Diff line
@@ -370,8 +370,6 @@ class UserController implements Handler.Callback {
    @GuardedBy("mLock")
    private boolean mIsBroadcastSentForSystemUserStarting;

    volatile boolean mBootCompleted;

    /**
     * In this mode, user is always stopped when switched out (unless overridden by the
     * {@code fw.stop_bg_users_on_switch} system property) but locking of user data is
@@ -710,19 +708,18 @@ class UserController implements Handler.Callback {
    }

    private void sendLockedBootCompletedBroadcast(IIntentReceiver receiver, @UserIdInt int userId) {
        if (android.os.Flags.allowPrivateProfile()
                && android.multiuser.Flags.enablePrivateSpaceFeatures()) {
            final UserInfo userInfo = getUserInfo(userId);
            if (userInfo != null && userInfo.isPrivateProfile()) {
                Slogf.i(TAG, "Skipping LOCKED_BOOT_COMPLETED for private profile user #" + userId);
                return;
            }
        }
        final Intent intent = new Intent(Intent.ACTION_LOCKED_BOOT_COMPLETED, null);
        intent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
        intent.addFlags(Intent.FLAG_RECEIVER_NO_ABORT
                | Intent.FLAG_RECEIVER_OFFLOAD
                | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);

        final UserInfo userInfo = getUserInfo(userId);
        if (userInfo != null && userInfo.isPrivateProfile()) {
            sendBroadcastLockedBootCompleteForPrivateProfileApps(intent, userId, receiver);
            return;
        }

        mInjector.broadcastIntent(intent, null, receiver, 0, null, null,
                new String[]{android.Manifest.permission.RECEIVE_BOOT_COMPLETED},
                AppOpsManager.OP_NONE,
@@ -732,6 +729,44 @@ class UserController implements Handler.Callback {
                Binder.getCallingUid(), Binder.getCallingPid(), userId);
    }

    /**
     * Only broadcast the LOCKED_BOOT_COMPLETED intent to allowlisted immediate receivers. Other
     * packages will receive the intent after they are started.
     */
    private void sendBroadcastLockedBootCompleteForPrivateProfileApps(Intent bootIntent,
            int userId,
            IIntentReceiver receiver) {
        if (!android.multiuser.Flags.enableMovingContentIntoPrivateSpace()) {
            Slogf.i(TAG, "Skipping LOCKED_BOOT_COMPLETED for private profile user #" + userId);
            return;
        }

        String[] packages = mInjector.getContext().getResources().getStringArray(
                R.array.config_privateSpaceBootCompletedImmediateReceivers);

        AtomicInteger remainingPackages = new AtomicInteger(packages.length);
        for (String packageName : packages) {
            Intent intent = new Intent(bootIntent);
            intent.setPackage(packageName);
            IIntentReceiver packageReceiver = new IIntentReceiver.Stub() {
                @Override
                public void performReceive(Intent intent, int resultCode, String data,
                        Bundle extras, boolean ordered, boolean sticky, int sendingUser)
                        throws RemoteException {
                    if (remainingPackages.decrementAndGet() == 0) {
                        receiver.performReceive(intent, resultCode, data, extras, ordered, sticky,
                                sendingUser);
                    }
                }
            };
            mInjector.broadcastIntent(intent, null, packageReceiver, 0, null, null,
                    new String[]{android.Manifest.permission.RECEIVE_BOOT_COMPLETED},
                    AppOpsManager.OP_NONE, getTemporaryAppAllowlistBroadcastOptions(
                            REASON_LOCKED_BOOT_COMPLETED).toBundle(), false, MY_PID, SYSTEM_UID,
                    Binder.getCallingUid(), Binder.getCallingPid(), userId);
        }
    }

    /**
     * Step from {@link UserState#STATE_RUNNING_LOCKED} to
     * {@link UserState#STATE_RUNNING_UNLOCKING}.
@@ -924,13 +959,6 @@ class UserController implements Handler.Callback {

        mHandler.obtainMessage(USER_UNLOCKED_MSG, userId, 0).sendToTarget();

        if (android.os.Flags.allowPrivateProfile()
                && android.multiuser.Flags.enablePrivateSpaceFeatures()) {
            if (userInfo.isPrivateProfile()) {
                Slogf.i(TAG, "Skipping BOOT_COMPLETED for private profile user #" + userId);
                return;
            }
        }
        Slogf.i(TAG, "Posting BOOT_COMPLETED user #" + userId);
        // Do not report secondary users, runtime restarts or first boot/upgrade
        if (userId == UserHandle.USER_SYSTEM
@@ -949,23 +977,58 @@ class UserController implements Handler.Callback {
        // we also send the boot_completed broadcast from that thread.
        final int callingUid = Binder.getCallingUid();
        final int callingPid = Binder.getCallingPid();

        if (userInfo.isPrivateProfile()) {
            sendBroadcastBootCompleteForPrivateProfileApps(bootIntent, userId, callingUid,
                    callingPid);
            return;
        }
        FgThread.getHandler().post(() -> {
            broadcastBootCompletedIntent(bootIntent, userId, callingUid, callingPid);
        });
    }

    /**
     * Only broadcast the BOOT_COMPLETED intent to allowlisted immediate receivers. Other packages
     * will receive the intent after they are started.
     */
    private void sendBroadcastBootCompleteForPrivateProfileApps(Intent bootIntent, int userId,
            int callingUid, int callingPid) {
        if (!android.multiuser.Flags.enableMovingContentIntoPrivateSpace()) {
            Slogf.i(TAG, "Skipping BOOT_COMPLETED for private profile user #" + userId);
            return;
        }

        FgThread.getHandler().post(() -> {
            String[] packages = mInjector.getContext().getResources().getStringArray(
                    R.array.config_privateSpaceBootCompletedImmediateReceivers);

            for (String packageName : packages) {
                Intent intent = new Intent(bootIntent);
                intent.setPackage(packageName);
                broadcastBootCompletedIntent(intent, userId, callingUid, callingPid);
            }
        });
    }

    private void broadcastBootCompletedIntent(Intent bootIntent, int userId, int callingUid,
            int callingPid) {
        mInjector.broadcastIntent(bootIntent, null,
                new IIntentReceiver.Stub() {
                    @Override
                    public void performReceive(Intent intent, int resultCode, String data,
                            Bundle extras, boolean ordered, boolean sticky, int sendingUser)
                            throws RemoteException {
                            Slogf.i(UserController.TAG, "Finished processing BOOT_COMPLETED for u"
                                    + userId);
                            mBootCompleted = true;
                        Slogf.i(UserController.TAG,
                                "Finished processing BOOT_COMPLETED for u" + userId
                                        + (Objects.isNull(bootIntent.getPackage()) ? ""
                                        : ", package: " + bootIntent.getPackage()));
                    }
                }, 0, null, null,
                new String[]{android.Manifest.permission.RECEIVE_BOOT_COMPLETED},
                AppOpsManager.OP_NONE,
                getTemporaryAppAllowlistBroadcastOptions(REASON_BOOT_COMPLETED).toBundle(),
                false, MY_PID, SYSTEM_UID, callingUid, callingPid, userId);
        });
    }

    /**