Merge "Partial Screen Sharing: Task Switcher - Add method exposing session to callback" into udc-qpr-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
718f34a72a
@@ -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