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

Commit 718f34a7 authored by Christian Göllner's avatar Christian Göllner Committed by Android (Google) Code Review
Browse files

Merge "Partial Screen Sharing: Task Switcher - Add method exposing session to...

Merge "Partial Screen Sharing: Task Switcher - Add method exposing session to callback" into udc-qpr-dev
parents c8a7e355 62a6b1f2
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(