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

Commit 12f5992e authored by Naomi Musgrave's avatar Naomi Musgrave
Browse files

[Partial Screenshare] introduce a hidden, permission-protected Activity API

Hidden Activity API allows a component holding permission
MANAGE_MEDIA_PROJECTION to declare that the result of setting up the
MediaProjection session should be returned immediately to the host VC app.

The host VC app will cycle through the resumed state when the activity
result is delivered to it, and return to its original state.

Manually tested with test MediaProjection app.

Test: atest WmTests:ActivityRecordTests
Bug: 230607871
Change-Id: Ic85fe8657b9e7f526bbc37a52011d16afa8aef2c
parent b8b70b0a
Loading
Loading
Loading
Loading
+14 −0
Original line number Diff line number Diff line
@@ -6432,6 +6432,20 @@ public class Activity extends ContextThemeWrapper
        }
    }

    /**
     * Ensures the activity's result is immediately returned to the caller when {@link #finish()}
     * is invoked
     *
     * <p>Should be invoked alongside {@link #setResult(int, Intent)}, so the provided results are
     * in place before finishing. Must only be invoked during MediaProjection setup.
     *
     * @hide
     */
    @RequiresPermission(android.Manifest.permission.MANAGE_MEDIA_PROJECTION)
    public final void setForceSendResultForMediaProjection() {
        ActivityClient.getInstance().setForceSendResultForMediaProjection(mToken);
    }

    /**
     * Call this to set the result that your activity will return to its
     * caller.
+10 −0
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@
package android.app;

import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.content.ComponentName;
import android.content.Intent;
import android.content.res.Configuration;
@@ -184,6 +185,15 @@ public class ActivityClient {
        }
    }

    @RequiresPermission(android.Manifest.permission.MANAGE_MEDIA_PROJECTION)
    void setForceSendResultForMediaProjection(IBinder token) {
        try {
            getActivityClientController().setForceSendResultForMediaProjection(token);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    public boolean isTopOfTask(IBinder token) {
        try {
            return getActivityClientController().isTopOfTask(token);
+6 −0
Original line number Diff line number Diff line
@@ -67,6 +67,12 @@ interface IActivityClientController {
    boolean finishActivityAffinity(in IBinder token);
    /** Finish all activities that were started for result from the specified activity. */
    void finishSubActivity(in IBinder token, in String resultWho, int requestCode);
    /**
     * Indicates that when the activity finsihes, the result should be immediately sent to the
     * originating activity. Must only be invoked during MediaProjection setup.
     */
    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_MEDIA_PROJECTION)")
    void setForceSendResultForMediaProjection(in IBinder token);

    boolean isTopOfTask(in IBinder token);
    boolean willActivityBeVisible(in IBinder token);
+20 −2
Original line number Diff line number Diff line
@@ -43,6 +43,7 @@ import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_N
import static com.android.server.wm.ActivityTaskManagerService.TAG_SWITCH;
import static com.android.server.wm.ActivityTaskManagerService.enforceNotIsolatedCaller;

import android.Manifest;
import android.annotation.ColorInt;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -462,8 +463,8 @@ class ActivityClientController extends IActivityClientController.Stub {
                    // Explicitly dismissing the activity so reset its relaunch flag.
                    r.mRelaunchReason = RELAUNCH_REASON_NONE;
                } else {
                    r.finishIfPossible(resultCode, resultData, resultGrants,
                            "app-request", true /* oomAdj */);
                    r.finishIfPossible(resultCode, resultData, resultGrants, "app-request",
                            true /* oomAdj */);
                    res = r.finishing;
                    if (!res) {
                        Slog.i(TAG, "Failed to finish by app-request");
@@ -524,6 +525,23 @@ class ActivityClientController extends IActivityClientController.Stub {
        }
    }

    @Override
    public void setForceSendResultForMediaProjection(IBinder token) {
        // Require that this is invoked only during MediaProjection setup.
        mService.mAmInternal.enforceCallingPermission(
                Manifest.permission.MANAGE_MEDIA_PROJECTION,
                "setForceSendResultForMediaProjection");

        final ActivityRecord r;
        synchronized (mGlobalLock) {
            r = ActivityRecord.isInRootTaskLocked(token);
            if (r == null || !r.isInHistory()) {
                return;
            }
            r.setForceSendResultForMediaProjection();
        }
    }

    @Override
    public boolean isTopOfTask(IBinder token) {
        synchronized (mGlobalLock) {
+75 −2
Original line number Diff line number Diff line
@@ -595,6 +595,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
    // pre-NYC apps that don't have a sense of being resized.
    int mRelaunchReason = RELAUNCH_REASON_NONE;

    private boolean mForceSendResultForMediaProjection = false;

    TaskDescription taskDescription; // the recents information for this activity

    // The locusId associated with this activity, if set.
@@ -3257,7 +3259,12 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
                mAtmService.mUgmInternal.grantUriPermissionUncheckedFromIntent(resultGrants,
                        resultTo.getUriPermissionsLocked());
            }
            if (mForceSendResultForMediaProjection) {
                resultTo.sendResult(this.getUid(), resultWho, requestCode, resultCode,
                        resultData, resultGrants, true /* forceSendForMediaProjection */);
            } else {
                resultTo.addResultLocked(this, resultWho, requestCode, resultCode, resultData);
            }
            resultTo = null;
        } else if (DEBUG_RESULTS) {
            Slog.v(TAG_RESULTS, "No result destination from " + this);
@@ -3451,6 +3458,10 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
        }
    }

    void setForceSendResultForMediaProjection() {
        mForceSendResultForMediaProjection = true;
    }

    private void prepareActivityHideTransitionAnimationIfOvarlay() {
        if (mTaskOverlay) {
            prepareActivityHideTransitionAnimation();
@@ -4541,6 +4552,12 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A

    void sendResult(int callingUid, String resultWho, int requestCode, int resultCode,
            Intent data, NeededUriGrants dataGrants) {
        sendResult(callingUid, resultWho, requestCode, resultCode, data, dataGrants,
                false /* forceSendForMediaProjection */);
    }

    private void sendResult(int callingUid, String resultWho, int requestCode, int resultCode,
            Intent data, NeededUriGrants dataGrants, boolean forceSendForMediaProjection) {
        if (callingUid > 0) {
            mAtmService.mUgmInternal.grantUriPermissionUncheckedFromIntent(dataGrants,
                    getUriPermissionsLocked());
@@ -4549,8 +4566,10 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
        if (DEBUG_RESULTS) {
            Slog.v(TAG, "Send activity result to " + this
                    + " : who=" + resultWho + " req=" + requestCode
                    + " res=" + resultCode + " data=" + data);
                    + " res=" + resultCode + " data=" + data
                    + " forceSendForMediaProjection=" + forceSendForMediaProjection);
        }

        if (isState(RESUMED) && attachedToProcess()) {
            try {
                final ArrayList<ResultInfo> list = new ArrayList<ResultInfo>();
@@ -4563,9 +4582,63 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
            }
        }

        // Schedule sending results now for Media Projection setup.
        if (forceSendForMediaProjection && attachedToProcess() && isState(STARTED, PAUSING, PAUSED,
                STOPPING, STOPPED)) {
            final ClientTransaction transaction = ClientTransaction.obtain(app.getThread(), token);
            // Build result to be returned immediately.
            transaction.addCallback(ActivityResultItem.obtain(
                    List.of(new ResultInfo(resultWho, requestCode, resultCode, data))));
            // When the activity result is delivered, the activity will transition to RESUMED.
            // Since the activity is only resumed so the result can be immediately delivered,
            // return it to its original lifecycle state.
            ActivityLifecycleItem lifecycleItem = getLifecycleItemForCurrentStateForResult();
            if (lifecycleItem != null) {
                transaction.setLifecycleStateRequest(lifecycleItem);
            } else {
                Slog.w(TAG, "Unable to get the lifecycle item for state " + mState
                        + " so couldn't immediately send result");
            }
            try {
                mAtmService.getLifecycleManager().scheduleTransaction(transaction);
            } catch (RemoteException e) {
                Slog.w(TAG, "Exception thrown sending result to " + this, e);
            }
        }

        addResultLocked(null /* from */, resultWho, requestCode, resultCode, data);
    }

    /**
     * Provides a lifecycle item for the current stat. Only to be used when force sending an
     * activity result (as part of MeidaProjection setup). Does not support the following states:
     * {@link State#INITIALIZING}, {@link State#RESTARTING_PROCESS},
     * {@link State#FINISHING}, {@link State#DESTROYING}, {@link State#DESTROYED}. It does not make
     * sense to force send a result to an activity in these states. Does not support
     * {@link State#RESUMED} since a resumed activity will end in the resumed state after handling
     * the result.
     *
     * @return an {@link ActivityLifecycleItem} for the current state, or {@code null} if the
     * state is not valid.
     */
    @Nullable
    private ActivityLifecycleItem getLifecycleItemForCurrentStateForResult() {
        switch (mState) {
            case STARTED:
                return StartActivityItem.obtain(null);
            case PAUSING:
            case PAUSED:
                return PauseActivityItem.obtain();
            case STOPPING:
            case STOPPED:
                return StopActivityItem.obtain(configChangeFlags);
            default:
                // Do not send a result immediately if the activity is in state INITIALIZING,
                // RESTARTING_PROCESS, FINISHING, DESTROYING, or DESTROYED.
                return null;
        }
    }

    private void addNewIntentLocked(ReferrerIntent intent) {
        if (newIntents == null) {
            newIntents = new ArrayList<>();
Loading