Merge "Partial Screen Sharing: Task Switcher - Add method exposing session to callback" into udc-qpr-dev

This commit is contained in:
Christian Göllner
2023-06-29 13:00:12 +00:00
committed by Android (Google) Code Review
5 changed files with 216 additions and 4 deletions

View File

@@ -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
);
}

View File

@@ -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));
}
}
}

View File

@@ -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) {

View File

@@ -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());

View File

@@ -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(