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

Commit 62a6b1f2 authored by Christian Göllner's avatar Christian Göllner
Browse files

Partial Screen Sharing: Task Switcher - Add method exposing session to callback

Adds a new method to MediaProjectionManager#Callback to inform when the
ContentRecordingSession was set.

This is needed for consumers that require more information about the recording
session, such as the content that is being recorded (screen vs task).

Bug: 286201261
Test: atest WmTests:WindowManagerServiceTests
Test: atest FrameworksServicesTests:MediaProjectionManagerServiceTest
Change-Id: Ie354d62dbc3896bd42079f2e5b5fb82cdb12706e
parent b6ca21a6
Loading
Loading
Loading
Loading
+13 −0
Original line number Original line Diff line number Diff line
@@ -17,9 +17,22 @@
package android.media.projection;
package android.media.projection;


import android.media.projection.MediaProjectionInfo;
import android.media.projection.MediaProjectionInfo;
import android.view.ContentRecordingSession;


/** {@hide} */
/** {@hide} */
oneway interface IMediaProjectionWatcherCallback {
oneway interface IMediaProjectionWatcherCallback {
    void onStart(in MediaProjectionInfo info);
    void onStart(in MediaProjectionInfo info);
    void onStop(in MediaProjectionInfo info);
    void onStop(in MediaProjectionInfo info);
    /**
     * Called when the {@link ContentRecordingSession} was set for the current media
     * projection.
     *
     * @param info    always present and contains information about the media projection host.
     * @param session the recording session for the current media projection. Can be
     *                {@code null} when the recording will stop.
     */
    void onRecordingSessionSet(
        in MediaProjectionInfo info,
        in @nullable ContentRecordingSession session
    );
}
}
+24 −0
Original line number Original line Diff line number Diff line
@@ -29,6 +29,7 @@ import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.ServiceManager;
import android.util.ArrayMap;
import android.util.ArrayMap;
import android.util.Log;
import android.util.Log;
import android.view.ContentRecordingSession;
import android.view.Surface;
import android.view.Surface;


import java.util.Map;
import java.util.Map;
@@ -300,7 +301,22 @@ public final class MediaProjectionManager {
    /** @hide */
    /** @hide */
    public static abstract class Callback {
    public static abstract class Callback {
        public abstract void onStart(MediaProjectionInfo info);
        public abstract void onStart(MediaProjectionInfo info);

        public abstract void onStop(MediaProjectionInfo info);
        public abstract void onStop(MediaProjectionInfo info);

        /**
         * Called when the {@link ContentRecordingSession} was set for the current media
         * projection.
         *
         * @param info    always present and contains information about the media projection host.
         * @param session the recording session for the current media projection. Can be
         *                {@code null} when the recording will stop.
         */
        public void onRecordingSessionSet(
                @NonNull MediaProjectionInfo info,
                @Nullable ContentRecordingSession session
        ) {
        }
    }
    }


    /** @hide */
    /** @hide */
@@ -335,5 +351,13 @@ public final class MediaProjectionManager {
                }
                }
            });
            });
        }
        }

        @Override
        public void onRecordingSessionSet(
                @NonNull final MediaProjectionInfo info,
                @Nullable final ContentRecordingSession session
        ) {
            mHandler.post(() -> mCallback.onRecordingSessionSet(info, session));
        }
    }
    }
}
}
+50 −4
Original line number Original line Diff line number Diff line
@@ -147,7 +147,7 @@ public final class MediaProjectionManagerService extends SystemService
        mInjector = injector;
        mInjector = injector;
        mClock = injector.createClock();
        mClock = injector.createClock();
        mDeathEaters = new ArrayMap<IBinder, IBinder.DeathRecipient>();
        mDeathEaters = new ArrayMap<IBinder, IBinder.DeathRecipient>();
        mCallbackDelegate = new CallbackDelegate();
        mCallbackDelegate = new CallbackDelegate(injector.createCallbackLooper());
        mAppOps = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
        mAppOps = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
        mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
        mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
        mPackageManager = mContext.getPackageManager();
        mPackageManager = mContext.getPackageManager();
@@ -182,6 +182,11 @@ public final class MediaProjectionManagerService extends SystemService
        Clock createClock() {
        Clock createClock() {
            return SystemClock::uptimeMillis;
            return SystemClock::uptimeMillis;
        }
        }

        /** Creates the {@link Looper} to be used when notifying callbacks. */
        Looper createCallbackLooper() {
            return Looper.getMainLooper();
        }
    }
    }


    @Override
    @Override
@@ -268,7 +273,8 @@ public final class MediaProjectionManagerService extends SystemService
        dispatchStop(projection);
        dispatchStop(projection);
    }
    }


    private void addCallback(final IMediaProjectionWatcherCallback callback) {
    @VisibleForTesting
    void addCallback(final IMediaProjectionWatcherCallback callback) {
        IBinder.DeathRecipient deathRecipient = new IBinder.DeathRecipient() {
        IBinder.DeathRecipient deathRecipient = new IBinder.DeathRecipient() {
            @Override
            @Override
            public void binderDied() {
            public void binderDied() {
@@ -315,6 +321,12 @@ public final class MediaProjectionManagerService extends SystemService
        mCallbackDelegate.dispatchStop(projection);
        mCallbackDelegate.dispatchStop(projection);
    }
    }


    private void dispatchSessionSet(
            @NonNull MediaProjectionInfo projectionInfo,
            @Nullable ContentRecordingSession session) {
        mCallbackDelegate.dispatchSession(projectionInfo, session);
    }

    /**
    /**
     * Returns {@code true} when updating the current mirroring session on WM succeeded, and
     * Returns {@code true} when updating the current mirroring session on WM succeeded, and
     * {@code false} otherwise.
     * {@code false} otherwise.
@@ -335,6 +347,7 @@ public final class MediaProjectionManagerService extends SystemService
            if (mProjectionGrant != null) {
            if (mProjectionGrant != null) {
                // Cache the session details.
                // Cache the session details.
                mProjectionGrant.mSession = incomingSession;
                mProjectionGrant.mSession = incomingSession;
                dispatchSessionSet(mProjectionGrant.getProjectionInfo(), incomingSession);
            }
            }
            return true;
            return true;
        }
        }
@@ -1155,8 +1168,8 @@ public final class MediaProjectionManagerService extends SystemService
        private Handler mHandler;
        private Handler mHandler;
        private final Object mLock = new Object();
        private final Object mLock = new Object();


        public CallbackDelegate() {
        CallbackDelegate(Looper callbackLooper) {
            mHandler = new Handler(Looper.getMainLooper(), null, true /*async*/);
            mHandler = new Handler(callbackLooper, null, true /*async*/);
            mClientCallbacks = new ArrayMap<IBinder, IMediaProjectionCallback>();
            mClientCallbacks = new ArrayMap<IBinder, IMediaProjectionCallback>();
            mWatcherCallbacks = new ArrayMap<IBinder, IMediaProjectionWatcherCallback>();
            mWatcherCallbacks = new ArrayMap<IBinder, IMediaProjectionWatcherCallback>();
        }
        }
@@ -1219,6 +1232,16 @@ public final class MediaProjectionManagerService extends SystemService
            }
            }
        }
        }


        public void dispatchSession(
                @NonNull MediaProjectionInfo projectionInfo,
                @Nullable ContentRecordingSession session) {
            synchronized (mLock) {
                for (IMediaProjectionWatcherCallback callback : mWatcherCallbacks.values()) {
                    mHandler.post(new WatcherSessionCallback(callback, projectionInfo, session));
                }
            }
        }

        public void dispatchResize(MediaProjection projection, int width, int height) {
        public void dispatchResize(MediaProjection projection, int width, int height) {
            if (projection == null) {
            if (projection == null) {
                Slog.e(TAG,
                Slog.e(TAG,
@@ -1335,6 +1358,29 @@ public final class MediaProjectionManagerService extends SystemService
        }
        }
    }
    }


    private static final class WatcherSessionCallback implements Runnable {
        private final IMediaProjectionWatcherCallback mCallback;
        private final MediaProjectionInfo mProjectionInfo;
        private final ContentRecordingSession mSession;

        WatcherSessionCallback(
                @NonNull IMediaProjectionWatcherCallback callback,
                @NonNull MediaProjectionInfo projectionInfo,
                @Nullable ContentRecordingSession session) {
            mCallback = callback;
            mProjectionInfo = projectionInfo;
            mSession = session;
        }

        @Override
        public void run() {
            try {
                mCallback.onRecordingSessionSet(mProjectionInfo, mSession);
            } catch (RemoteException e) {
                Slog.w(TAG, "Failed to notify content recording session changed", e);
            }
        }
    }


    private static String typeToString(int type) {
    private static String typeToString(int type) {
        switch (type) {
        switch (type) {
+70 −0
Original line number Original line Diff line number Diff line
@@ -38,6 +38,7 @@ import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.testng.Assert.assertThrows;
import static org.testng.Assert.assertThrows;


import android.app.ActivityManagerInternal;
import android.app.ActivityManagerInternal;
@@ -49,10 +50,14 @@ import android.content.pm.PackageManager.ApplicationInfoFlags;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.PackageManager.NameNotFoundException;
import android.media.projection.IMediaProjection;
import android.media.projection.IMediaProjection;
import android.media.projection.IMediaProjectionCallback;
import android.media.projection.IMediaProjectionCallback;
import android.media.projection.IMediaProjectionWatcherCallback;
import android.media.projection.ReviewGrantedConsentResult;
import android.media.projection.ReviewGrantedConsentResult;
import android.os.Binder;
import android.os.IBinder;
import android.os.IBinder;
import android.os.Looper;
import android.os.RemoteException;
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserHandle;
import android.os.test.TestLooper;
import android.platform.test.annotations.Presubmit;
import android.platform.test.annotations.Presubmit;
import android.view.ContentRecordingSession;
import android.view.ContentRecordingSession;


@@ -86,6 +91,7 @@ public class MediaProjectionManagerServiceTest {
    private static final int UID = 10;
    private static final int UID = 10;
    private static final String PACKAGE_NAME = "test.package";
    private static final String PACKAGE_NAME = "test.package";
    private final ApplicationInfo mAppInfo = new ApplicationInfo();
    private final ApplicationInfo mAppInfo = new ApplicationInfo();
    private final TestLooper mTestLooper = new TestLooper();
    private static final ContentRecordingSession DISPLAY_SESSION =
    private static final ContentRecordingSession DISPLAY_SESSION =
            ContentRecordingSession.createDisplaySession(DEFAULT_DISPLAY);
            ContentRecordingSession.createDisplaySession(DEFAULT_DISPLAY);
    // Callback registered by an app on a MediaProjection instance.
    // Callback registered by an app on a MediaProjection instance.
@@ -110,6 +116,14 @@ public class MediaProjectionManagerServiceTest {
                }
                }
            };
            };


    private final MediaProjectionManagerService.Injector mTestLooperInjector =
            new MediaProjectionManagerService.Injector() {
                @Override
                Looper createCallbackLooper() {
                    return mTestLooper.getLooper();
                }
            };

    private Context mContext;
    private Context mContext;
    private MediaProjectionManagerService mService;
    private MediaProjectionManagerService mService;
    private OffsettableClock mClock;
    private OffsettableClock mClock;
@@ -122,12 +136,15 @@ public class MediaProjectionManagerServiceTest {
    private WindowManagerInternal mWindowManagerInternal;
    private WindowManagerInternal mWindowManagerInternal;
    @Mock
    @Mock
    private PackageManager mPackageManager;
    private PackageManager mPackageManager;
    @Mock
    private IMediaProjectionWatcherCallback mWatcherCallback;
    @Captor
    @Captor
    private ArgumentCaptor<ContentRecordingSession> mSessionCaptor;
    private ArgumentCaptor<ContentRecordingSession> mSessionCaptor;


    @Before
    @Before
    public void setup() throws Exception {
    public void setup() throws Exception {
        MockitoAnnotations.initMocks(this);
        MockitoAnnotations.initMocks(this);
        when(mWatcherCallback.asBinder()).thenReturn(new Binder());


        LocalServices.removeServiceForTest(ActivityManagerInternal.class);
        LocalServices.removeServiceForTest(ActivityManagerInternal.class);
        LocalServices.addService(ActivityManagerInternal.class, mAmInternal);
        LocalServices.addService(ActivityManagerInternal.class, mAmInternal);
@@ -671,6 +688,59 @@ public class MediaProjectionManagerServiceTest {
        assertThat(mService.isCurrentProjection(projection)).isTrue();
        assertThat(mService.isCurrentProjection(projection)).isTrue();
    }
    }


    @Test
    public void setContentRecordingSession_successful_notifiesListeners()
            throws Exception {
        mService.addCallback(mWatcherCallback);
        MediaProjectionManagerService.MediaProjection projection = startProjectionPreconditions();
        projection.start(mIMediaProjectionCallback);

        doReturn(true).when(mWindowManagerInternal).setContentRecordingSession(
                any(ContentRecordingSession.class));
        mService.setContentRecordingSession(DISPLAY_SESSION);

        verify(mWatcherCallback).onRecordingSessionSet(
                projection.getProjectionInfo(),
                DISPLAY_SESSION
        );
    }

    @Test
    public void setContentRecordingSession_notifiesListenersOnCallbackLooper()
            throws Exception {
        mService = new MediaProjectionManagerService(mContext, mTestLooperInjector);
        mService.addCallback(mWatcherCallback);
        MediaProjectionManagerService.MediaProjection projection = startProjectionPreconditions();
        projection.start(mIMediaProjectionCallback);
        doReturn(true).when(mWindowManagerInternal).setContentRecordingSession(
                any(ContentRecordingSession.class));

        mService.setContentRecordingSession(DISPLAY_SESSION);
        // Callback not notified yet, as test looper hasn't dispatched the message yet
        verify(mWatcherCallback, never()).onRecordingSessionSet(any(), any());

        mTestLooper.dispatchAll();
        // Message dispatched on test looper. Callback should now be notified.
        verify(mWatcherCallback).onRecordingSessionSet(
                projection.getProjectionInfo(),
                DISPLAY_SESSION
        );
    }

    @Test
    public void setContentRecordingSession_failure_doesNotNotifyListeners()
            throws Exception {
        mService.addCallback(mWatcherCallback);
        MediaProjectionManagerService.MediaProjection projection = startProjectionPreconditions();
        projection.start(mIMediaProjectionCallback);

        doReturn(false).when(mWindowManagerInternal).setContentRecordingSession(
                any(ContentRecordingSession.class));
        mService.setContentRecordingSession(DISPLAY_SESSION);

        verify(mWatcherCallback, never()).onRecordingSessionSet(any(), any());
    }

    private void verifySetSessionWithContent(@ContentRecordingSession.RecordContent int content) {
    private void verifySetSessionWithContent(@ContentRecordingSession.RecordContent int content) {
        verify(mWindowManagerInternal, atLeastOnce()).setContentRecordingSession(
        verify(mWindowManagerInternal, atLeastOnce()).setContentRecordingSession(
                mSessionCaptor.capture());
                mSessionCaptor.capture());
+59 −0
Original line number Original line Diff line number Diff line
@@ -81,6 +81,7 @@ import android.os.UserHandle;
import android.platform.test.annotations.Presubmit;
import android.platform.test.annotations.Presubmit;
import android.util.DisplayMetrics;
import android.util.DisplayMetrics;
import android.util.MergedConfiguration;
import android.util.MergedConfiguration;
import android.view.ContentRecordingSession;
import android.view.IWindow;
import android.view.IWindow;
import android.view.IWindowSessionCallback;
import android.view.IWindowSessionCallback;
import android.view.InputChannel;
import android.view.InputChannel;
@@ -101,6 +102,7 @@ import androidx.test.platform.app.InstrumentationRegistry;


import com.android.compatibility.common.util.AdoptShellPermissionsRule;
import com.android.compatibility.common.util.AdoptShellPermissionsRule;
import com.android.internal.os.IResultReceiver;
import com.android.internal.os.IResultReceiver;
import com.android.server.LocalServices;


import org.junit.Rule;
import org.junit.Rule;
import org.junit.Test;
import org.junit.Test;
@@ -760,6 +762,63 @@ public class WindowManagerServiceTests extends WindowTestsBase {
        assertThat(wct).isNull();
        assertThat(wct).isNull();
    }
    }


    @Test
    public void setContentRecordingSession_sessionNull_returnsTrue() {
        WindowManagerInternal wmInternal = LocalServices.getService(WindowManagerInternal.class);

        boolean result = wmInternal.setContentRecordingSession(/* incomingSession= */ null);

        assertThat(result).isTrue();
    }

    @Test
    public void setContentRecordingSession_sessionContentDisplay_returnsTrue() {
        WindowManagerInternal wmInternal = LocalServices.getService(WindowManagerInternal.class);
        ContentRecordingSession session = ContentRecordingSession.createDisplaySession(
                DEFAULT_DISPLAY);

        boolean result = wmInternal.setContentRecordingSession(session);

        assertThat(result).isTrue();
    }

    @Test
    public void setContentRecordingSession_sessionContentTask_noMatchingTask_returnsFalse() {
        WindowManagerInternal wmInternal = LocalServices.getService(WindowManagerInternal.class);
        IBinder launchCookie = new Binder();
        ContentRecordingSession session = ContentRecordingSession.createTaskSession(launchCookie);

        boolean result = wmInternal.setContentRecordingSession(session);

        assertThat(result).isFalse();
    }

    @Test
    public void setContentRecordingSession_sessionContentTask_matchingTask_returnsTrue() {
        WindowManagerInternal wmInternal = LocalServices.getService(WindowManagerInternal.class);
        ActivityRecord activityRecord = createActivityRecord(createTask(mDefaultDisplay));
        ContentRecordingSession session = ContentRecordingSession.createTaskSession(
                activityRecord.mLaunchCookie);

        boolean result = wmInternal.setContentRecordingSession(session);

        assertThat(result).isTrue();
    }

    @Test
    public void setContentRecordingSession_matchingTask_mutatesSessionWithWindowContainerToken() {
        WindowManagerInternal wmInternal = LocalServices.getService(WindowManagerInternal.class);
        Task task = createTask(mDefaultDisplay);
        ActivityRecord activityRecord = createActivityRecord(task);
        ContentRecordingSession session = ContentRecordingSession.createTaskSession(
                activityRecord.mLaunchCookie);

        wmInternal.setContentRecordingSession(session);

        assertThat(session.getTokenToRecord()).isEqualTo(
                task.mRemoteToken.toWindowContainerToken().asBinder());
    }

    @Test
    @Test
    public void testisLetterboxBackgroundMultiColored() {
    public void testisLetterboxBackgroundMultiColored() {
        assertThat(setupLetterboxConfigurationWithBackgroundType(
        assertThat(setupLetterboxConfigurationWithBackgroundType(