Merge "[Partial Screensharing] Add a temporary entrypoint in SysUI screen recorder" into tm-qpr-dev am: 5837f00409

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/19416386

Change-Id: I7bad3de5bb150a770e30d0da39a40ac968d7c34c
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
Nick Chameyev
2022-07-28 23:54:26 +00:00
committed by Automerger Merge Worker
11 changed files with 340 additions and 24 deletions

View File

@@ -148,6 +148,20 @@
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_weight="1"/> android:layout_weight="1"/>
<!-- Temporary entrypoint for the partial screensharing used for teamfooding -->
<!-- TODO(b/236838395) remove this and use redesigned dialog -->
<TextView
android:id="@+id/button_app"
android:visibility="gone"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="0"
android:layout_gravity="end"
android:layout_marginEnd="8dp"
android:text="App"
style="@style/Widget.Dialog.Button.BorderButton" />
<TextView <TextView
android:id="@+id/button_start" android:id="@+id/button_start"
android:layout_width="wrap_content" android:layout_width="wrap_content"

View File

@@ -22,6 +22,7 @@ import android.media.projection.MediaProjectionManager.EXTRA_MEDIA_PROJECTION
import android.os.Binder import android.os.Binder
import android.os.Bundle import android.os.Bundle
import android.os.IBinder import android.os.IBinder
import android.os.ResultReceiver
import android.view.View import android.view.View
import com.android.internal.app.ChooserActivity import com.android.internal.app.ChooserActivity
import com.android.internal.app.chooser.NotSelectableTargetInfo import com.android.internal.app.chooser.NotSelectableTargetInfo
@@ -103,6 +104,19 @@ class MediaProjectionAppSelectorActivity @Inject constructor(
} }
private fun onTargetActivityLaunched(launchToken: IBinder) { private fun onTargetActivityLaunched(launchToken: IBinder) {
if (intent.hasExtra(EXTRA_CAPTURE_REGION_RESULT_RECEIVER)) {
// The client requested to return the result in the result receiver instead of
// activity result, let's send the media projection to the result receiver
val resultReceiver = intent
.getParcelableExtra(EXTRA_CAPTURE_REGION_RESULT_RECEIVER,
ResultReceiver::class.java) as ResultReceiver
val captureRegion = MediaProjectionCaptureTarget(launchToken)
val data = Bundle().apply {
putParcelable(KEY_CAPTURE_TARGET, captureRegion)
}
resultReceiver.send(RESULT_OK, data)
} else {
// Return the media projection instance as activity result
val mediaProjectionBinder = intent.getIBinderExtra(EXTRA_MEDIA_PROJECTION) val mediaProjectionBinder = intent.getIBinderExtra(EXTRA_MEDIA_PROJECTION)
val projection = IMediaProjection.Stub.asInterface(mediaProjectionBinder) val projection = IMediaProjection.Stub.asInterface(mediaProjectionBinder)
@@ -112,8 +126,20 @@ class MediaProjectionAppSelectorActivity @Inject constructor(
intent.putExtra(EXTRA_MEDIA_PROJECTION, projection.asBinder()) intent.putExtra(EXTRA_MEDIA_PROJECTION, projection.asBinder())
setResult(RESULT_OK, intent) setResult(RESULT_OK, intent)
setForceSendResultForMediaProjection() setForceSendResultForMediaProjection()
}
finish() finish()
} }
override fun shouldGetOnlyDefaultActivities() = false override fun shouldGetOnlyDefaultActivities() = false
companion object {
/**
* When EXTRA_CAPTURE_REGION_RESULT_RECEIVER is passed as intent extra
* the activity will send the [CaptureRegion] to the result receiver
* instead of returning media projection instance through activity result.
*/
const val EXTRA_CAPTURE_REGION_RESULT_RECEIVER = "capture_region_result_receiver"
const val KEY_CAPTURE_TARGET = "capture_region"
}
} }

View File

@@ -0,0 +1,49 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.media
import android.os.IBinder
import android.os.Parcel
import android.os.Parcelable
/**
* Class that represents an area that should be captured.
* Currently it has only a launch cookie that represents a task but
* we potentially could add more identifiers e.g. for a pair of tasks.
*/
data class MediaProjectionCaptureTarget(
val launchCookie: IBinder?
): Parcelable {
constructor(parcel: Parcel) : this(parcel.readStrongBinder())
override fun writeToParcel(dest: Parcel, flags: Int) {
dest.writeStrongBinder(launchCookie)
}
override fun describeContents(): Int = 0
companion object CREATOR : Parcelable.Creator<MediaProjectionCaptureTarget> {
override fun createFromParcel(parcel: Parcel): MediaProjectionCaptureTarget {
return MediaProjectionCaptureTarget(parcel)
}
override fun newArray(size: Int): Array<MediaProjectionCaptureTarget?> {
return arrayOfNulls(size)
}
}
}

View File

@@ -34,6 +34,7 @@ import com.android.systemui.animation.DialogCuj;
import com.android.systemui.animation.DialogLaunchAnimator; import com.android.systemui.animation.DialogLaunchAnimator;
import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.qs.QSTile; import com.android.systemui.plugins.qs.QSTile;
@@ -61,6 +62,7 @@ public class ScreenRecordTile extends QSTileImpl<QSTile.BooleanState>
private final KeyguardStateController mKeyguardStateController; private final KeyguardStateController mKeyguardStateController;
private final Callback mCallback = new Callback(); private final Callback mCallback = new Callback();
private final DialogLaunchAnimator mDialogLaunchAnimator; private final DialogLaunchAnimator mDialogLaunchAnimator;
private final FeatureFlags mFlags;
private long mMillisUntilFinished = 0; private long mMillisUntilFinished = 0;
@@ -71,6 +73,7 @@ public class ScreenRecordTile extends QSTileImpl<QSTile.BooleanState>
@Main Handler mainHandler, @Main Handler mainHandler,
FalsingManager falsingManager, FalsingManager falsingManager,
MetricsLogger metricsLogger, MetricsLogger metricsLogger,
FeatureFlags flags,
StatusBarStateController statusBarStateController, StatusBarStateController statusBarStateController,
ActivityStarter activityStarter, ActivityStarter activityStarter,
QSLogger qsLogger, QSLogger qsLogger,
@@ -83,6 +86,7 @@ public class ScreenRecordTile extends QSTileImpl<QSTile.BooleanState>
statusBarStateController, activityStarter, qsLogger); statusBarStateController, activityStarter, qsLogger);
mController = controller; mController = controller;
mController.observe(this, mCallback); mController.observe(this, mCallback);
mFlags = flags;
mKeyguardDismissUtil = keyguardDismissUtil; mKeyguardDismissUtil = keyguardDismissUtil;
mKeyguardStateController = keyguardStateController; mKeyguardStateController = keyguardStateController;
mDialogLaunchAnimator = dialogLaunchAnimator; mDialogLaunchAnimator = dialogLaunchAnimator;
@@ -164,8 +168,8 @@ public class ScreenRecordTile extends QSTileImpl<QSTile.BooleanState>
mDialogLaunchAnimator.disableAllCurrentDialogsExitAnimations(); mDialogLaunchAnimator.disableAllCurrentDialogsExitAnimations();
getHost().collapsePanels(); getHost().collapsePanels();
}; };
ScreenRecordDialog dialog = mController.createScreenRecordDialog(mContext, ScreenRecordDialog dialog = mController.createScreenRecordDialog(mContext, mFlags,
onStartRecordingClicked); mDialogLaunchAnimator, mActivityStarter, onStartRecordingClicked);
ActivityStarter.OnDismissAction dismissAction = () -> { ActivityStarter.OnDismissAction dismissAction = () -> {
if (shouldAnimateFromView) { if (shouldAnimateFromView) {

View File

@@ -29,8 +29,11 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting; import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.animation.DialogLaunchAnimator;
import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.settings.UserContextProvider; import com.android.systemui.settings.UserContextProvider;
import com.android.systemui.statusbar.policy.CallbackController; import com.android.systemui.statusbar.policy.CallbackController;
@@ -94,9 +97,11 @@ public class RecordingController
} }
/** Create a dialog to show screen recording options to the user. */ /** Create a dialog to show screen recording options to the user. */
public ScreenRecordDialog createScreenRecordDialog(Context context, public ScreenRecordDialog createScreenRecordDialog(Context context, FeatureFlags flags,
DialogLaunchAnimator dialogLaunchAnimator, ActivityStarter activityStarter,
@Nullable Runnable onStartRecordingClicked) { @Nullable Runnable onStartRecordingClicked) {
return new ScreenRecordDialog(context, this, mUserContextProvider, onStartRecordingClicked); return new ScreenRecordDialog(context, this, activityStarter, mUserContextProvider,
flags, dialogLaunchAnimator, onStartRecordingClicked);
} }
/** /**

View File

@@ -16,6 +16,7 @@
package com.android.systemui.screenrecord; package com.android.systemui.screenrecord;
import android.annotation.Nullable;
import android.app.Notification; import android.app.Notification;
import android.app.NotificationChannel; import android.app.NotificationChannel;
import android.app.NotificationManager; import android.app.NotificationManager;
@@ -42,6 +43,7 @@ import com.android.internal.logging.UiEventLogger;
import com.android.systemui.R; import com.android.systemui.R;
import com.android.systemui.dagger.qualifiers.LongRunning; import com.android.systemui.dagger.qualifiers.LongRunning;
import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.media.MediaProjectionCaptureTarget;
import com.android.systemui.screenrecord.ScreenMediaRecorder.ScreenMediaRecorderListener; import com.android.systemui.screenrecord.ScreenMediaRecorder.ScreenMediaRecorderListener;
import com.android.systemui.settings.UserContextProvider; import com.android.systemui.settings.UserContextProvider;
import com.android.systemui.statusbar.phone.KeyguardDismissUtil; import com.android.systemui.statusbar.phone.KeyguardDismissUtil;
@@ -67,6 +69,7 @@ public class RecordingService extends Service implements ScreenMediaRecorderList
private static final String EXTRA_PATH = "extra_path"; private static final String EXTRA_PATH = "extra_path";
private static final String EXTRA_AUDIO_SOURCE = "extra_useAudio"; private static final String EXTRA_AUDIO_SOURCE = "extra_useAudio";
private static final String EXTRA_SHOW_TAPS = "extra_showTaps"; private static final String EXTRA_SHOW_TAPS = "extra_showTaps";
private static final String EXTRA_CAPTURE_TARGET = "extra_captureTarget";
private static final String ACTION_START = "com.android.systemui.screenrecord.START"; private static final String ACTION_START = "com.android.systemui.screenrecord.START";
private static final String ACTION_STOP = "com.android.systemui.screenrecord.STOP"; private static final String ACTION_STOP = "com.android.systemui.screenrecord.STOP";
@@ -110,14 +113,18 @@ public class RecordingService extends Service implements ScreenMediaRecorderList
* @param audioSource The ordinal value of the audio source * @param audioSource The ordinal value of the audio source
* {@link com.android.systemui.screenrecord.ScreenRecordingAudioSource} * {@link com.android.systemui.screenrecord.ScreenRecordingAudioSource}
* @param showTaps True to make touches visible while recording * @param showTaps True to make touches visible while recording
* @param captureTarget pass this parameter to capture a specific part instead
* of the full screen
*/ */
public static Intent getStartIntent(Context context, int resultCode, public static Intent getStartIntent(Context context, int resultCode,
int audioSource, boolean showTaps) { int audioSource, boolean showTaps,
@Nullable MediaProjectionCaptureTarget captureTarget) {
return new Intent(context, RecordingService.class) return new Intent(context, RecordingService.class)
.setAction(ACTION_START) .setAction(ACTION_START)
.putExtra(EXTRA_RESULT_CODE, resultCode) .putExtra(EXTRA_RESULT_CODE, resultCode)
.putExtra(EXTRA_AUDIO_SOURCE, audioSource) .putExtra(EXTRA_AUDIO_SOURCE, audioSource)
.putExtra(EXTRA_SHOW_TAPS, showTaps); .putExtra(EXTRA_SHOW_TAPS, showTaps)
.putExtra(EXTRA_CAPTURE_TARGET, captureTarget);
} }
@Override @Override
@@ -136,6 +143,9 @@ public class RecordingService extends Service implements ScreenMediaRecorderList
.values()[intent.getIntExtra(EXTRA_AUDIO_SOURCE, 0)]; .values()[intent.getIntExtra(EXTRA_AUDIO_SOURCE, 0)];
Log.d(TAG, "recording with audio source" + mAudioSource); Log.d(TAG, "recording with audio source" + mAudioSource);
mShowTaps = intent.getBooleanExtra(EXTRA_SHOW_TAPS, false); mShowTaps = intent.getBooleanExtra(EXTRA_SHOW_TAPS, false);
MediaProjectionCaptureTarget captureTarget =
intent.getParcelableExtra(EXTRA_CAPTURE_TARGET,
MediaProjectionCaptureTarget.class);
mOriginalShowTaps = Settings.System.getInt( mOriginalShowTaps = Settings.System.getInt(
getApplicationContext().getContentResolver(), getApplicationContext().getContentResolver(),
@@ -148,6 +158,7 @@ public class RecordingService extends Service implements ScreenMediaRecorderList
mMainHandler, mMainHandler,
currentUserId, currentUserId,
mAudioSource, mAudioSource,
captureTarget,
this this
); );

View File

@@ -51,6 +51,7 @@ import android.util.Size;
import android.view.Surface; import android.view.Surface;
import android.view.WindowManager; import android.view.WindowManager;
import com.android.systemui.media.MediaProjectionCaptureTarget;
import java.io.File; import java.io.File;
import java.io.Closeable; import java.io.Closeable;
import java.io.IOException; import java.io.IOException;
@@ -85,6 +86,7 @@ public class ScreenMediaRecorder extends MediaProjection.Callback {
private ScreenRecordingMuxer mMuxer; private ScreenRecordingMuxer mMuxer;
private ScreenInternalAudioRecorder mAudio; private ScreenInternalAudioRecorder mAudio;
private ScreenRecordingAudioSource mAudioSource; private ScreenRecordingAudioSource mAudioSource;
private final MediaProjectionCaptureTarget mCaptureRegion;
private final Handler mHandler; private final Handler mHandler;
private Context mContext; private Context mContext;
@@ -92,10 +94,12 @@ public class ScreenMediaRecorder extends MediaProjection.Callback {
public ScreenMediaRecorder(Context context, Handler handler, public ScreenMediaRecorder(Context context, Handler handler,
int user, ScreenRecordingAudioSource audioSource, int user, ScreenRecordingAudioSource audioSource,
MediaProjectionCaptureTarget captureRegion,
ScreenMediaRecorderListener listener) { ScreenMediaRecorderListener listener) {
mContext = context; mContext = context;
mHandler = handler; mHandler = handler;
mUser = user; mUser = user;
mCaptureRegion = captureRegion;
mListener = listener; mListener = listener;
mAudioSource = audioSource; mAudioSource = audioSource;
} }
@@ -108,9 +112,11 @@ public class ScreenMediaRecorder extends MediaProjection.Callback {
IMediaProjection proj = null; IMediaProjection proj = null;
proj = mediaService.createProjection(mUser, mContext.getPackageName(), proj = mediaService.createProjection(mUser, mContext.getPackageName(),
MediaProjectionManager.TYPE_SCREEN_CAPTURE, false); MediaProjectionManager.TYPE_SCREEN_CAPTURE, false);
IBinder projection = proj.asBinder(); IMediaProjection projection = IMediaProjection.Stub.asInterface(proj.asBinder());
mMediaProjection = new MediaProjection(mContext, if (mCaptureRegion != null) {
IMediaProjection.Stub.asInterface(projection)); projection.setLaunchCookie(mCaptureRegion.getLaunchCookie());
}
mMediaProjection = new MediaProjection(mContext, projection);
mMediaProjection.registerCallback(this, mHandler); mMediaProjection.registerCallback(this, mHandler);
File cacheDir = mContext.getCacheDir(); File cacheDir = mContext.getCacheDir();

View File

@@ -16,6 +16,10 @@
package com.android.systemui.screenrecord; package com.android.systemui.screenrecord;
import static android.app.Activity.RESULT_OK;
import static com.android.systemui.media.MediaProjectionAppSelectorActivity.EXTRA_CAPTURE_REGION_RESULT_RECEIVER;
import static com.android.systemui.media.MediaProjectionAppSelectorActivity.KEY_CAPTURE_TARGET;
import static com.android.systemui.screenrecord.ScreenRecordingAudioSource.INTERNAL; import static com.android.systemui.screenrecord.ScreenRecordingAudioSource.INTERNAL;
import static com.android.systemui.screenrecord.ScreenRecordingAudioSource.MIC; import static com.android.systemui.screenrecord.ScreenRecordingAudioSource.MIC;
import static com.android.systemui.screenrecord.ScreenRecordingAudioSource.MIC_AND_INTERNAL; import static com.android.systemui.screenrecord.ScreenRecordingAudioSource.MIC_AND_INTERNAL;
@@ -24,8 +28,13 @@ import static com.android.systemui.screenrecord.ScreenRecordingAudioSource.NONE;
import android.app.Activity; import android.app.Activity;
import android.app.PendingIntent; import android.app.PendingIntent;
import android.content.Context; import android.content.Context;
import android.content.Intent;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.ResultReceiver;
import android.view.Gravity; import android.view.Gravity;
import android.view.View;
import android.view.Window; import android.view.Window;
import android.view.WindowManager; import android.view.WindowManager;
import android.widget.ArrayAdapter; import android.widget.ArrayAdapter;
@@ -36,6 +45,13 @@ import android.widget.TextView;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import com.android.systemui.R; import com.android.systemui.R;
import com.android.systemui.animation.ActivityLaunchAnimator;
import com.android.systemui.animation.DialogLaunchAnimator;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
import com.android.systemui.media.MediaProjectionAppSelectorActivity;
import com.android.systemui.media.MediaProjectionCaptureTarget;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.settings.UserContextProvider; import com.android.systemui.settings.UserContextProvider;
import com.android.systemui.statusbar.phone.SystemUIDialog; import com.android.systemui.statusbar.phone.SystemUIDialog;
@@ -55,15 +71,23 @@ public class ScreenRecordDialog extends SystemUIDialog {
private final UserContextProvider mUserContextProvider; private final UserContextProvider mUserContextProvider;
@Nullable @Nullable
private final Runnable mOnStartRecordingClicked; private final Runnable mOnStartRecordingClicked;
private final ActivityStarter mActivityStarter;
private final FeatureFlags mFlags;
private final DialogLaunchAnimator mDialogLaunchAnimator;
private Switch mTapsSwitch; private Switch mTapsSwitch;
private Switch mAudioSwitch; private Switch mAudioSwitch;
private Spinner mOptions; private Spinner mOptions;
public ScreenRecordDialog(Context context, RecordingController controller, public ScreenRecordDialog(Context context, RecordingController controller,
UserContextProvider userContextProvider, @Nullable Runnable onStartRecordingClicked) { ActivityStarter activityStarter, UserContextProvider userContextProvider,
FeatureFlags flags, DialogLaunchAnimator dialogLaunchAnimator,
@Nullable Runnable onStartRecordingClicked) {
super(context); super(context);
mController = controller; mController = controller;
mUserContextProvider = userContextProvider; mUserContextProvider = userContextProvider;
mActivityStarter = activityStarter;
mDialogLaunchAnimator = dialogLaunchAnimator;
mFlags = flags;
mOnStartRecordingClicked = onStartRecordingClicked; mOnStartRecordingClicked = onStartRecordingClicked;
} }
@@ -91,10 +115,36 @@ public class ScreenRecordDialog extends SystemUIDialog {
mOnStartRecordingClicked.run(); mOnStartRecordingClicked.run();
} }
requestScreenCapture(); // Start full-screen recording
requestScreenCapture(/* captureTarget= */ null);
dismiss(); dismiss();
}); });
if (mFlags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING)) {
TextView appBtn = findViewById(R.id.button_app);
appBtn.setVisibility(View.VISIBLE);
appBtn.setOnClickListener(v -> {
Intent intent = new Intent(getContext(), MediaProjectionAppSelectorActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
// We can't start activity for result here so we use result receiver to get
// the selected target to capture
intent.putExtra(EXTRA_CAPTURE_REGION_RESULT_RECEIVER,
new CaptureTargetResultReceiver());
ActivityLaunchAnimator.Controller animationController =
mDialogLaunchAnimator.createActivityLaunchController(appBtn);
if (animationController == null) {
dismiss();
}
mActivityStarter.startActivity(intent, /* dismissShade= */ true,
animationController);
});
}
mAudioSwitch = findViewById(R.id.screenrecord_audio_switch); mAudioSwitch = findViewById(R.id.screenrecord_audio_switch);
mTapsSwitch = findViewById(R.id.screenrecord_taps_switch); mTapsSwitch = findViewById(R.id.screenrecord_taps_switch);
mOptions = findViewById(R.id.screen_recording_options); mOptions = findViewById(R.id.screen_recording_options);
@@ -108,7 +158,12 @@ public class ScreenRecordDialog extends SystemUIDialog {
}); });
} }
private void requestScreenCapture() { /**
* Starts screen capture after some countdown
* @param captureTarget target to capture (could be e.g. a task) or
* null to record the whole screen
*/
private void requestScreenCapture(@Nullable MediaProjectionCaptureTarget captureTarget) {
Context userContext = mUserContextProvider.getUserContext(); Context userContext = mUserContextProvider.getUserContext();
boolean showTaps = mTapsSwitch.isChecked(); boolean showTaps = mTapsSwitch.isChecked();
ScreenRecordingAudioSource audioMode = mAudioSwitch.isChecked() ScreenRecordingAudioSource audioMode = mAudioSwitch.isChecked()
@@ -118,7 +173,7 @@ public class ScreenRecordDialog extends SystemUIDialog {
RecordingService.REQUEST_CODE, RecordingService.REQUEST_CODE,
RecordingService.getStartIntent( RecordingService.getStartIntent(
userContext, Activity.RESULT_OK, userContext, Activity.RESULT_OK,
audioMode.ordinal(), showTaps), audioMode.ordinal(), showTaps, captureTarget),
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE); PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
PendingIntent stopIntent = PendingIntent.getService(userContext, PendingIntent stopIntent = PendingIntent.getService(userContext,
RecordingService.REQUEST_CODE, RecordingService.REQUEST_CODE,
@@ -126,4 +181,22 @@ public class ScreenRecordDialog extends SystemUIDialog {
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE); PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
mController.startCountdown(DELAY_MS, INTERVAL_MS, startIntent, stopIntent); mController.startCountdown(DELAY_MS, INTERVAL_MS, startIntent, stopIntent);
} }
private class CaptureTargetResultReceiver extends ResultReceiver {
CaptureTargetResultReceiver() {
super(new Handler(Looper.getMainLooper()));
}
@Override
protected void onReceiveResult(int resultCode, Bundle resultData) {
if (resultCode == RESULT_OK) {
MediaProjectionCaptureTarget captureTarget = resultData
.getParcelable(KEY_CAPTURE_TARGET, MediaProjectionCaptureTarget.class);
// Start recording of the selected target
requestScreenCapture(captureTarget);
}
}
}
} }

View File

@@ -22,6 +22,7 @@ import static junit.framework.Assert.assertTrue;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
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.mockito.Mockito.when;
@@ -38,6 +39,7 @@ import com.android.systemui.R;
import com.android.systemui.SysuiTestCase; import com.android.systemui.SysuiTestCase;
import com.android.systemui.animation.DialogLaunchAnimator; import com.android.systemui.animation.DialogLaunchAnimator;
import com.android.systemui.classifier.FalsingManagerFake; import com.android.systemui.classifier.FalsingManagerFake;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.qs.QSTileHost; import com.android.systemui.qs.QSTileHost;
@@ -73,6 +75,8 @@ public class ScreenRecordTileTest extends SysuiTestCase {
@Mock @Mock
private QSLogger mQSLogger; private QSLogger mQSLogger;
@Mock @Mock
private FeatureFlags mFeatureFlags;
@Mock
private KeyguardStateController mKeyguardStateController; private KeyguardStateController mKeyguardStateController;
@Mock @Mock
private DialogLaunchAnimator mDialogLaunchAnimator; private DialogLaunchAnimator mDialogLaunchAnimator;
@@ -94,6 +98,7 @@ public class ScreenRecordTileTest extends SysuiTestCase {
new Handler(mTestableLooper.getLooper()), new Handler(mTestableLooper.getLooper()),
new FalsingManagerFake(), new FalsingManagerFake(),
mMetricsLogger, mMetricsLogger,
mFeatureFlags,
mStatusBarStateController, mStatusBarStateController,
mActivityStarter, mActivityStarter,
mQSLogger, mQSLogger,
@@ -125,7 +130,8 @@ public class ScreenRecordTileTest extends SysuiTestCase {
mTestableLooper.processAllMessages(); mTestableLooper.processAllMessages();
ArgumentCaptor<Runnable> onStartRecordingClicked = ArgumentCaptor.forClass(Runnable.class); ArgumentCaptor<Runnable> onStartRecordingClicked = ArgumentCaptor.forClass(Runnable.class);
verify(mController).createScreenRecordDialog(any(), onStartRecordingClicked.capture()); verify(mController).createScreenRecordDialog(any(), eq(mFeatureFlags),
eq(mDialogLaunchAnimator), eq(mActivityStarter), onStartRecordingClicked.capture());
// When starting the recording, we collapse the shade and disable the dialog animation. // When starting the recording, we collapse the shade and disable the dialog animation.
assertNotNull(onStartRecordingClicked.getValue()); assertNotNull(onStartRecordingClicked.getValue());

View File

@@ -30,6 +30,7 @@ import static org.mockito.Mockito.verify;
import android.app.Notification; import android.app.Notification;
import android.app.NotificationManager; import android.app.NotificationManager;
import android.content.Intent; import android.content.Intent;
import android.os.Binder;
import android.os.Handler; import android.os.Handler;
import android.os.RemoteException; import android.os.RemoteException;
import android.testing.AndroidTestingRunner; import android.testing.AndroidTestingRunner;
@@ -38,6 +39,7 @@ import androidx.test.filters.SmallTest;
import com.android.internal.logging.UiEventLogger; import com.android.internal.logging.UiEventLogger;
import com.android.systemui.SysuiTestCase; import com.android.systemui.SysuiTestCase;
import com.android.systemui.media.MediaProjectionCaptureTarget;
import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.settings.UserContextProvider; import com.android.systemui.settings.UserContextProvider;
import com.android.systemui.statusbar.phone.KeyguardDismissUtil; import com.android.systemui.statusbar.phone.KeyguardDismissUtil;
@@ -108,8 +110,17 @@ public class RecordingServiceTest extends SysuiTestCase {
} }
@Test @Test
public void testLogStartRecording() { public void testLogStartFullScreenRecording() {
Intent startIntent = RecordingService.getStartIntent(mContext, 0, 0, false); Intent startIntent = RecordingService.getStartIntent(mContext, 0, 0, false, null);
mRecordingService.onStartCommand(startIntent, 0, 0);
verify(mUiEventLogger, times(1)).log(Events.ScreenRecordEvent.SCREEN_RECORD_START);
}
@Test
public void testLogStartPartialRecording() {
MediaProjectionCaptureTarget target = new MediaProjectionCaptureTarget(new Binder());
Intent startIntent = RecordingService.getStartIntent(mContext, 0, 0, false, target);
mRecordingService.onStartCommand(startIntent, 0, 0); mRecordingService.onStartCommand(startIntent, 0, 0);
verify(mUiEventLogger, times(1)).log(Events.ScreenRecordEvent.SCREEN_RECORD_START); verify(mUiEventLogger, times(1)).log(Events.ScreenRecordEvent.SCREEN_RECORD_START);
@@ -142,7 +153,7 @@ public class RecordingServiceTest extends SysuiTestCase {
// When the screen recording does not start properly // When the screen recording does not start properly
doThrow(new RuntimeException("fail")).when(mScreenMediaRecorder).start(); doThrow(new RuntimeException("fail")).when(mScreenMediaRecorder).start();
Intent startIntent = RecordingService.getStartIntent(mContext, 0, 0, false); Intent startIntent = RecordingService.getStartIntent(mContext, 0, 0, false, null);
mRecordingService.onStartCommand(startIntent, 0, 0); mRecordingService.onStartCommand(startIntent, 0, 0);
// Then the state is set to not recording // Then the state is set to not recording

View File

@@ -0,0 +1,111 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.screenrecord
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.view.View
import androidx.test.filters.SmallTest
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.DialogLaunchAnimator
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.settings.UserContextProvider
import com.google.common.truth.Truth.assertThat
import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.MockitoAnnotations
import org.mockito.Mockito.`when` as whenever
@SmallTest
@RunWith(AndroidTestingRunner::class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
class ScreenRecordDialogTest : SysuiTestCase() {
@Mock
private lateinit var starter: ActivityStarter
@Mock
private lateinit var controller: RecordingController
@Mock
private lateinit var userContextProvider: UserContextProvider
@Mock
private lateinit var flags: FeatureFlags
@Mock
private lateinit var dialogLaunchAnimator: DialogLaunchAnimator
@Mock
private lateinit var onStartRecordingClicked: Runnable
private lateinit var dialog: ScreenRecordDialog
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
dialog = ScreenRecordDialog(
context, controller, starter, userContextProvider, flags, dialogLaunchAnimator,
onStartRecordingClicked
)
}
@After
fun teardown() {
if (::dialog.isInitialized) {
dialog.dismiss()
}
}
@Test
fun testShowDialog_partialScreenSharingDisabled_appButtonIsNotVisible() {
whenever(flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING)).thenReturn(false)
dialog.show()
val visibility = dialog.requireViewById<View>(R.id.button_app).visibility
assertThat(visibility).isEqualTo(View.GONE)
}
@Test
fun testShowDialog_partialScreenSharingEnabled_appButtonIsVisible() {
whenever(flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING)).thenReturn(true)
dialog.show()
val visibility = dialog.requireViewById<View>(R.id.button_app).visibility
assertThat(visibility).isEqualTo(View.VISIBLE)
}
}