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
This commit is contained in:
@@ -17,9 +17,22 @@
|
||||
package android.media.projection;
|
||||
|
||||
import android.media.projection.MediaProjectionInfo;
|
||||
import android.view.ContentRecordingSession;
|
||||
|
||||
/** {@hide} */
|
||||
oneway interface IMediaProjectionWatcherCallback {
|
||||
void onStart(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
|
||||
);
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ import android.os.RemoteException;
|
||||
import android.os.ServiceManager;
|
||||
import android.util.ArrayMap;
|
||||
import android.util.Log;
|
||||
import android.view.ContentRecordingSession;
|
||||
import android.view.Surface;
|
||||
|
||||
import java.util.Map;
|
||||
@@ -300,7 +301,22 @@ public final class MediaProjectionManager {
|
||||
/** @hide */
|
||||
public static abstract class Callback {
|
||||
public abstract void onStart(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 */
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -147,7 +147,7 @@ public final class MediaProjectionManagerService extends SystemService
|
||||
mInjector = injector;
|
||||
mClock = injector.createClock();
|
||||
mDeathEaters = new ArrayMap<IBinder, IBinder.DeathRecipient>();
|
||||
mCallbackDelegate = new CallbackDelegate();
|
||||
mCallbackDelegate = new CallbackDelegate(injector.createCallbackLooper());
|
||||
mAppOps = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
|
||||
mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
|
||||
mPackageManager = mContext.getPackageManager();
|
||||
@@ -182,6 +182,11 @@ public final class MediaProjectionManagerService extends SystemService
|
||||
Clock createClock() {
|
||||
return SystemClock::uptimeMillis;
|
||||
}
|
||||
|
||||
/** Creates the {@link Looper} to be used when notifying callbacks. */
|
||||
Looper createCallbackLooper() {
|
||||
return Looper.getMainLooper();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -268,7 +273,8 @@ public final class MediaProjectionManagerService extends SystemService
|
||||
dispatchStop(projection);
|
||||
}
|
||||
|
||||
private void addCallback(final IMediaProjectionWatcherCallback callback) {
|
||||
@VisibleForTesting
|
||||
void addCallback(final IMediaProjectionWatcherCallback callback) {
|
||||
IBinder.DeathRecipient deathRecipient = new IBinder.DeathRecipient() {
|
||||
@Override
|
||||
public void binderDied() {
|
||||
@@ -315,6 +321,12 @@ public final class MediaProjectionManagerService extends SystemService
|
||||
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
|
||||
* {@code false} otherwise.
|
||||
@@ -335,6 +347,7 @@ public final class MediaProjectionManagerService extends SystemService
|
||||
if (mProjectionGrant != null) {
|
||||
// Cache the session details.
|
||||
mProjectionGrant.mSession = incomingSession;
|
||||
dispatchSessionSet(mProjectionGrant.getProjectionInfo(), incomingSession);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -1155,8 +1168,8 @@ public final class MediaProjectionManagerService extends SystemService
|
||||
private Handler mHandler;
|
||||
private final Object mLock = new Object();
|
||||
|
||||
public CallbackDelegate() {
|
||||
mHandler = new Handler(Looper.getMainLooper(), null, true /*async*/);
|
||||
CallbackDelegate(Looper callbackLooper) {
|
||||
mHandler = new Handler(callbackLooper, null, true /*async*/);
|
||||
mClientCallbacks = new ArrayMap<IBinder, IMediaProjectionCallback>();
|
||||
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) {
|
||||
if (projection == null) {
|
||||
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) {
|
||||
switch (type) {
|
||||
|
||||
@@ -38,6 +38,7 @@ import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.testng.Assert.assertThrows;
|
||||
|
||||
import android.app.ActivityManagerInternal;
|
||||
@@ -49,10 +50,14 @@ import android.content.pm.PackageManager.ApplicationInfoFlags;
|
||||
import android.content.pm.PackageManager.NameNotFoundException;
|
||||
import android.media.projection.IMediaProjection;
|
||||
import android.media.projection.IMediaProjectionCallback;
|
||||
import android.media.projection.IMediaProjectionWatcherCallback;
|
||||
import android.media.projection.ReviewGrantedConsentResult;
|
||||
import android.os.Binder;
|
||||
import android.os.IBinder;
|
||||
import android.os.Looper;
|
||||
import android.os.RemoteException;
|
||||
import android.os.UserHandle;
|
||||
import android.os.test.TestLooper;
|
||||
import android.platform.test.annotations.Presubmit;
|
||||
import android.view.ContentRecordingSession;
|
||||
|
||||
@@ -86,6 +91,7 @@ public class MediaProjectionManagerServiceTest {
|
||||
private static final int UID = 10;
|
||||
private static final String PACKAGE_NAME = "test.package";
|
||||
private final ApplicationInfo mAppInfo = new ApplicationInfo();
|
||||
private final TestLooper mTestLooper = new TestLooper();
|
||||
private static final ContentRecordingSession DISPLAY_SESSION =
|
||||
ContentRecordingSession.createDisplaySession(DEFAULT_DISPLAY);
|
||||
// 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 MediaProjectionManagerService mService;
|
||||
private OffsettableClock mClock;
|
||||
@@ -122,12 +136,15 @@ public class MediaProjectionManagerServiceTest {
|
||||
private WindowManagerInternal mWindowManagerInternal;
|
||||
@Mock
|
||||
private PackageManager mPackageManager;
|
||||
@Mock
|
||||
private IMediaProjectionWatcherCallback mWatcherCallback;
|
||||
@Captor
|
||||
private ArgumentCaptor<ContentRecordingSession> mSessionCaptor;
|
||||
|
||||
@Before
|
||||
public void setup() throws Exception {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
when(mWatcherCallback.asBinder()).thenReturn(new Binder());
|
||||
|
||||
LocalServices.removeServiceForTest(ActivityManagerInternal.class);
|
||||
LocalServices.addService(ActivityManagerInternal.class, mAmInternal);
|
||||
@@ -671,6 +688,59 @@ public class MediaProjectionManagerServiceTest {
|
||||
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) {
|
||||
verify(mWindowManagerInternal, atLeastOnce()).setContentRecordingSession(
|
||||
mSessionCaptor.capture());
|
||||
|
||||
@@ -81,6 +81,7 @@ import android.os.UserHandle;
|
||||
import android.platform.test.annotations.Presubmit;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.util.MergedConfiguration;
|
||||
import android.view.ContentRecordingSession;
|
||||
import android.view.IWindow;
|
||||
import android.view.IWindowSessionCallback;
|
||||
import android.view.InputChannel;
|
||||
@@ -101,6 +102,7 @@ import androidx.test.platform.app.InstrumentationRegistry;
|
||||
|
||||
import com.android.compatibility.common.util.AdoptShellPermissionsRule;
|
||||
import com.android.internal.os.IResultReceiver;
|
||||
import com.android.server.LocalServices;
|
||||
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
@@ -760,6 +762,63 @@ public class WindowManagerServiceTests extends WindowTestsBase {
|
||||
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
|
||||
public void testisLetterboxBackgroundMultiColored() {
|
||||
assertThat(setupLetterboxConfigurationWithBackgroundType(
|
||||
|
||||
Reference in New Issue
Block a user