Merge "[Partial Screensharing] Permission Dialog initial version" into tm-qpr-dev
This commit is contained in:
86
packages/SystemUI/res/layout/screen_record_options.xml
Normal file
86
packages/SystemUI/res/layout/screen_record_options.xml
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
<!--
|
||||||
|
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.
|
||||||
|
-->
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical">
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="@dimen/screenrecord_option_icon_size"
|
||||||
|
android:layout_height="@dimen/screenrecord_option_icon_size"
|
||||||
|
android:src="@drawable/ic_mic_26dp"
|
||||||
|
android:tint="?android:attr/textColorSecondary"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:layout_weight="0"
|
||||||
|
android:layout_marginRight="@dimen/screenrecord_option_padding"
|
||||||
|
android:importantForAccessibility="no"/>
|
||||||
|
<Spinner
|
||||||
|
android:id="@+id/screen_recording_options"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:minHeight="48dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:popupBackground="@drawable/screenrecord_spinner_background"
|
||||||
|
android:dropDownWidth="274dp"
|
||||||
|
android:importantForAccessibility="yes"/>
|
||||||
|
<Switch
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:minWidth="48dp"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
android:layout_weight="0"
|
||||||
|
android:layout_gravity="end"
|
||||||
|
android:id="@+id/screenrecord_audio_switch"
|
||||||
|
style="@style/ScreenRecord.Switch"
|
||||||
|
android:importantForAccessibility="yes"/>
|
||||||
|
</LinearLayout>
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:layout_marginTop="@dimen/screenrecord_option_padding">
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="@dimen/screenrecord_option_icon_size"
|
||||||
|
android:layout_height="@dimen/screenrecord_option_icon_size"
|
||||||
|
android:layout_weight="0"
|
||||||
|
android:src="@drawable/ic_touch"
|
||||||
|
android:tint="?android:attr/textColorSecondary"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:layout_marginRight="@dimen/screenrecord_option_padding"
|
||||||
|
android:importantForAccessibility="no"/>
|
||||||
|
<TextView
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:minHeight="48dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:text="@string/screenrecord_taps_label"
|
||||||
|
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||||
|
android:fontFamily="@*android:string/config_headlineFontFamily"
|
||||||
|
android:textColor="?android:attr/textColorPrimary"
|
||||||
|
android:contentDescription="@string/screenrecord_taps_label"/>
|
||||||
|
<Switch
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:minWidth="48dp"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
android:layout_weight="0"
|
||||||
|
android:id="@+id/screenrecord_taps_switch"
|
||||||
|
style="@style/ScreenRecord.Switch"
|
||||||
|
android:importantForAccessibility="yes"/>
|
||||||
|
</LinearLayout>
|
||||||
|
</LinearLayout>
|
||||||
94
packages/SystemUI/res/layout/screen_share_dialog.xml
Normal file
94
packages/SystemUI/res/layout/screen_share_dialog.xml
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
<!--
|
||||||
|
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.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<!-- Scrollview is necessary to fit everything in landscape layout -->
|
||||||
|
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:id="@+id/screen_share_permission_dialog"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingStart="@dimen/dialog_side_padding"
|
||||||
|
android:paddingEnd="@dimen/dialog_side_padding"
|
||||||
|
android:paddingTop="@dimen/dialog_top_padding"
|
||||||
|
android:paddingBottom="@dimen/dialog_bottom_padding"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:gravity="center_horizontal">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="@dimen/screenrecord_logo_size"
|
||||||
|
android:layout_height="@dimen/screenrecord_logo_size"
|
||||||
|
android:src="@drawable/ic_screenrecord"
|
||||||
|
android:tint="@color/screenrecord_icon_color"
|
||||||
|
android:importantForAccessibility="no"/>
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/screen_share_dialog_title"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textAppearance="?android:attr/textAppearanceLarge"
|
||||||
|
android:fontFamily="@*android:string/config_headlineFontFamily"
|
||||||
|
android:layout_marginTop="22dp"
|
||||||
|
android:layout_marginBottom="15dp"/>
|
||||||
|
<Spinner
|
||||||
|
android:id="@+id/screen_share_mode_spinner"
|
||||||
|
android:layout_width="320dp"
|
||||||
|
android:layout_height="72dp"
|
||||||
|
android:layout_marginTop="24dp"
|
||||||
|
android:layout_marginBottom="24dp" />
|
||||||
|
<ViewStub
|
||||||
|
android:id="@+id/options_stub"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"/>
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/text_warning"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/screenrecord_description"
|
||||||
|
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||||
|
android:textColor="?android:textColorSecondary"
|
||||||
|
android:gravity="start"
|
||||||
|
android:layout_marginBottom="20dp"/>
|
||||||
|
|
||||||
|
<!-- Buttons -->
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:layout_marginTop="36dp">
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/button_cancel"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="0"
|
||||||
|
android:text="@string/cancel"
|
||||||
|
style="@style/Widget.Dialog.Button.BorderButton" />
|
||||||
|
<Space
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_weight="1"/>
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/button_start"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="0"
|
||||||
|
android:text="@string/screenrecord_start"
|
||||||
|
style="@style/Widget.Dialog.Button" />
|
||||||
|
</LinearLayout>
|
||||||
|
</LinearLayout>
|
||||||
|
</ScrollView>
|
||||||
@@ -18,6 +18,9 @@ package com.android.systemui.media;
|
|||||||
|
|
||||||
import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
|
import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
|
||||||
|
|
||||||
|
import static com.android.systemui.screenrecord.ScreenShareOptionKt.ENTIRE_SCREEN;
|
||||||
|
import static com.android.systemui.screenrecord.ScreenShareOptionKt.SINGLE_APP;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.app.AlertDialog;
|
import android.app.AlertDialog;
|
||||||
import android.content.DialogInterface;
|
import android.content.DialogInterface;
|
||||||
@@ -44,6 +47,8 @@ import com.android.systemui.Dependency;
|
|||||||
import com.android.systemui.R;
|
import com.android.systemui.R;
|
||||||
import com.android.systemui.flags.FeatureFlags;
|
import com.android.systemui.flags.FeatureFlags;
|
||||||
import com.android.systemui.flags.Flags;
|
import com.android.systemui.flags.Flags;
|
||||||
|
import com.android.systemui.screenrecord.MediaProjectionPermissionDialog;
|
||||||
|
import com.android.systemui.screenrecord.ScreenShareOption;
|
||||||
import com.android.systemui.statusbar.phone.SystemUIDialog;
|
import com.android.systemui.statusbar.phone.SystemUIDialog;
|
||||||
import com.android.systemui.util.Utils;
|
import com.android.systemui.util.Utils;
|
||||||
|
|
||||||
@@ -102,7 +107,9 @@ public class MediaProjectionPermissionActivity extends Activity
|
|||||||
|
|
||||||
CharSequence dialogText = null;
|
CharSequence dialogText = null;
|
||||||
CharSequence dialogTitle = null;
|
CharSequence dialogTitle = null;
|
||||||
|
String appName = null;
|
||||||
if (Utils.isHeadlessRemoteDisplayProvider(packageManager, mPackageName)) {
|
if (Utils.isHeadlessRemoteDisplayProvider(packageManager, mPackageName)) {
|
||||||
|
// TODO(b/253438807): handle special app name
|
||||||
dialogText = getString(R.string.media_projection_dialog_service_text);
|
dialogText = getString(R.string.media_projection_dialog_service_text);
|
||||||
dialogTitle = getString(R.string.media_projection_dialog_service_title);
|
dialogTitle = getString(R.string.media_projection_dialog_service_title);
|
||||||
} else {
|
} else {
|
||||||
@@ -132,7 +139,7 @@ public class MediaProjectionPermissionActivity extends Activity
|
|||||||
|
|
||||||
String unsanitizedAppName = TextUtils.ellipsize(label,
|
String unsanitizedAppName = TextUtils.ellipsize(label,
|
||||||
paint, MAX_APP_NAME_SIZE_PX, TextUtils.TruncateAt.END).toString();
|
paint, MAX_APP_NAME_SIZE_PX, TextUtils.TruncateAt.END).toString();
|
||||||
String appName = BidiFormatter.getInstance().unicodeWrap(unsanitizedAppName);
|
appName = BidiFormatter.getInstance().unicodeWrap(unsanitizedAppName);
|
||||||
|
|
||||||
String actionText = getString(R.string.media_projection_dialog_text, appName);
|
String actionText = getString(R.string.media_projection_dialog_text, appName);
|
||||||
SpannableString message = new SpannableString(actionText);
|
SpannableString message = new SpannableString(actionText);
|
||||||
@@ -146,27 +153,28 @@ public class MediaProjectionPermissionActivity extends Activity
|
|||||||
dialogTitle = getString(R.string.media_projection_dialog_title, appName);
|
dialogTitle = getString(R.string.media_projection_dialog_title, appName);
|
||||||
}
|
}
|
||||||
|
|
||||||
AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this,
|
|
||||||
R.style.Theme_SystemUI_Dialog)
|
|
||||||
.setTitle(dialogTitle)
|
|
||||||
.setIcon(R.drawable.ic_media_projection_permission)
|
|
||||||
.setMessage(dialogText)
|
|
||||||
.setPositiveButton(R.string.media_projection_action_text, this)
|
|
||||||
.setNeutralButton(android.R.string.cancel, this)
|
|
||||||
.setOnCancelListener(this);
|
|
||||||
|
|
||||||
if (isPartialScreenSharingEnabled()) {
|
if (isPartialScreenSharingEnabled()) {
|
||||||
// This is a temporary entry point before we have a new permission dialog
|
mDialog = new MediaProjectionPermissionDialog(this, () -> {
|
||||||
// TODO(b/233183090): this activity should be redesigned to have a dropdown selector
|
ScreenShareOption selectedOption =
|
||||||
dialogBuilder.setNegativeButton("App", this);
|
((MediaProjectionPermissionDialog) mDialog).getSelectedScreenShareOption();
|
||||||
|
grantMediaProjectionPermission(selectedOption.getMode());
|
||||||
|
}, appName);
|
||||||
|
} else {
|
||||||
|
AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this,
|
||||||
|
R.style.Theme_SystemUI_Dialog)
|
||||||
|
.setTitle(dialogTitle)
|
||||||
|
.setIcon(R.drawable.ic_media_projection_permission)
|
||||||
|
.setMessage(dialogText)
|
||||||
|
.setPositiveButton(R.string.media_projection_action_text, this)
|
||||||
|
.setNeutralButton(android.R.string.cancel, this);
|
||||||
|
mDialog = dialogBuilder.create();
|
||||||
}
|
}
|
||||||
|
|
||||||
mDialog = dialogBuilder.create();
|
|
||||||
|
|
||||||
SystemUIDialog.registerDismissListener(mDialog);
|
SystemUIDialog.registerDismissListener(mDialog);
|
||||||
SystemUIDialog.applyFlags(mDialog);
|
SystemUIDialog.applyFlags(mDialog);
|
||||||
SystemUIDialog.setDialogSize(mDialog);
|
SystemUIDialog.setDialogSize(mDialog);
|
||||||
|
|
||||||
|
mDialog.setOnCancelListener(this);
|
||||||
mDialog.create();
|
mDialog.create();
|
||||||
mDialog.getButton(DialogInterface.BUTTON_POSITIVE).setFilterTouchesWhenObscured(true);
|
mDialog.getButton(DialogInterface.BUTTON_POSITIVE).setFilterTouchesWhenObscured(true);
|
||||||
|
|
||||||
@@ -186,12 +194,17 @@ public class MediaProjectionPermissionActivity extends Activity
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onClick(DialogInterface dialog, int which) {
|
public void onClick(DialogInterface dialog, int which) {
|
||||||
|
if (which == AlertDialog.BUTTON_POSITIVE) {
|
||||||
|
grantMediaProjectionPermission(ENTIRE_SCREEN);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void grantMediaProjectionPermission(int screenShareMode) {
|
||||||
try {
|
try {
|
||||||
if (which == AlertDialog.BUTTON_POSITIVE) {
|
if (screenShareMode == ENTIRE_SCREEN) {
|
||||||
setResult(RESULT_OK, getMediaProjectionIntent(mUid, mPackageName));
|
setResult(RESULT_OK, getMediaProjectionIntent(mUid, mPackageName));
|
||||||
}
|
}
|
||||||
|
if (isPartialScreenSharingEnabled() && screenShareMode == SINGLE_APP) {
|
||||||
if (isPartialScreenSharingEnabled() && which == AlertDialog.BUTTON_NEGATIVE) {
|
|
||||||
IMediaProjection projection = createProjection(mUid, mPackageName);
|
IMediaProjection projection = createProjection(mUid, mPackageName);
|
||||||
final Intent intent = new Intent(this, MediaProjectionAppSelectorActivity.class);
|
final Intent intent = new Intent(this, MediaProjectionAppSelectorActivity.class);
|
||||||
intent.putExtra(MediaProjectionManager.EXTRA_MEDIA_PROJECTION,
|
intent.putExtra(MediaProjectionManager.EXTRA_MEDIA_PROJECTION,
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
package com.android.systemui.qs.tiles;
|
package com.android.systemui.qs.tiles;
|
||||||
|
|
||||||
|
import android.app.Dialog;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.os.Looper;
|
import android.os.Looper;
|
||||||
@@ -43,7 +44,6 @@ import com.android.systemui.qs.QSHost;
|
|||||||
import com.android.systemui.qs.logging.QSLogger;
|
import com.android.systemui.qs.logging.QSLogger;
|
||||||
import com.android.systemui.qs.tileimpl.QSTileImpl;
|
import com.android.systemui.qs.tileimpl.QSTileImpl;
|
||||||
import com.android.systemui.screenrecord.RecordingController;
|
import com.android.systemui.screenrecord.RecordingController;
|
||||||
import com.android.systemui.screenrecord.ScreenRecordDialog;
|
|
||||||
import com.android.systemui.statusbar.phone.KeyguardDismissUtil;
|
import com.android.systemui.statusbar.phone.KeyguardDismissUtil;
|
||||||
import com.android.systemui.statusbar.policy.KeyguardStateController;
|
import com.android.systemui.statusbar.policy.KeyguardStateController;
|
||||||
|
|
||||||
@@ -170,9 +170,9 @@ public class ScreenRecordTile extends QSTileImpl<QSTile.BooleanState>
|
|||||||
mDialogLaunchAnimator.disableAllCurrentDialogsExitAnimations();
|
mDialogLaunchAnimator.disableAllCurrentDialogsExitAnimations();
|
||||||
getHost().collapsePanels();
|
getHost().collapsePanels();
|
||||||
};
|
};
|
||||||
ScreenRecordDialog dialog = mController.createScreenRecordDialog(mContext, mFlags,
|
|
||||||
mDialogLaunchAnimator, mActivityStarter, onStartRecordingClicked);
|
|
||||||
|
|
||||||
|
Dialog dialog = mController.createScreenRecordDialog(mContext, mFlags,
|
||||||
|
mDialogLaunchAnimator, mActivityStarter, onStartRecordingClicked);
|
||||||
ActivityStarter.OnDismissAction dismissAction = () -> {
|
ActivityStarter.OnDismissAction dismissAction = () -> {
|
||||||
if (shouldAnimateFromView) {
|
if (shouldAnimateFromView) {
|
||||||
mDialogLaunchAnimator.showFromView(dialog, view, new DialogCuj(
|
mDialogLaunchAnimator.showFromView(dialog, view, new DialogCuj(
|
||||||
|
|||||||
@@ -0,0 +1,110 @@
|
|||||||
|
/*
|
||||||
|
* 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.content.Context
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.Gravity
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewStub
|
||||||
|
import android.view.WindowManager
|
||||||
|
import android.widget.AdapterView
|
||||||
|
import android.widget.ArrayAdapter
|
||||||
|
import android.widget.Spinner
|
||||||
|
import android.widget.TextView
|
||||||
|
import androidx.annotation.LayoutRes
|
||||||
|
import androidx.annotation.StringRes
|
||||||
|
import com.android.systemui.R
|
||||||
|
import com.android.systemui.statusbar.phone.SystemUIDialog
|
||||||
|
|
||||||
|
/** Base permission dialog for screen share and recording */
|
||||||
|
open class BaseScreenSharePermissionDialog(
|
||||||
|
context: Context?,
|
||||||
|
private val screenShareOptions: List<ScreenShareOption>,
|
||||||
|
private val appName: String?
|
||||||
|
) : SystemUIDialog(context), AdapterView.OnItemSelectedListener {
|
||||||
|
private lateinit var dialogTitle: TextView
|
||||||
|
private lateinit var startButton: TextView
|
||||||
|
private lateinit var warning: TextView
|
||||||
|
private lateinit var screenShareModeSpinner: Spinner
|
||||||
|
var selectedScreenShareOption: ScreenShareOption = screenShareOptions.first()
|
||||||
|
|
||||||
|
public override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
window.apply {
|
||||||
|
addPrivateFlags(WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS)
|
||||||
|
setGravity(Gravity.CENTER)
|
||||||
|
}
|
||||||
|
setContentView(R.layout.screen_share_dialog)
|
||||||
|
dialogTitle = findViewById(R.id.screen_share_dialog_title)
|
||||||
|
warning = findViewById(R.id.text_warning)
|
||||||
|
startButton = findViewById(R.id.button_start)
|
||||||
|
findViewById<TextView>(R.id.button_cancel).setOnClickListener { dismiss() }
|
||||||
|
initScreenShareOptions()
|
||||||
|
createOptionsView(getOptionsViewLayoutId())
|
||||||
|
}
|
||||||
|
|
||||||
|
protected fun initScreenShareOptions() {
|
||||||
|
selectedScreenShareOption = screenShareOptions.first()
|
||||||
|
warning.text = warningText
|
||||||
|
initScreenShareSpinner()
|
||||||
|
}
|
||||||
|
|
||||||
|
private val warningText: String
|
||||||
|
get() = context.getString(selectedScreenShareOption.warningText, appName)
|
||||||
|
|
||||||
|
private fun initScreenShareSpinner() {
|
||||||
|
val options = screenShareOptions.map { context.getString(it.spinnerText) }.toTypedArray()
|
||||||
|
val adapter =
|
||||||
|
ArrayAdapter(context.applicationContext, android.R.layout.simple_spinner_item, options)
|
||||||
|
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
|
||||||
|
screenShareModeSpinner = findViewById(R.id.screen_share_mode_spinner)
|
||||||
|
screenShareModeSpinner.adapter = adapter
|
||||||
|
screenShareModeSpinner.onItemSelectedListener = this
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onItemSelected(adapterView: AdapterView<*>?, view: View, pos: Int, id: Long) {
|
||||||
|
selectedScreenShareOption = screenShareOptions[pos]
|
||||||
|
warning.text = warningText
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onNothingSelected(parent: AdapterView<*>?) {}
|
||||||
|
|
||||||
|
/** Protected methods for the text updates & functionality */
|
||||||
|
protected fun setDialogTitle(@StringRes stringId: Int) {
|
||||||
|
val title = context.getString(stringId, appName)
|
||||||
|
dialogTitle.text = title
|
||||||
|
}
|
||||||
|
|
||||||
|
protected fun setStartButtonText(@StringRes stringId: Int) {
|
||||||
|
startButton.setText(stringId)
|
||||||
|
}
|
||||||
|
|
||||||
|
protected fun setStartButtonOnClickListener(listener: View.OnClickListener?) {
|
||||||
|
startButton.setOnClickListener(listener)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create additional options that is shown under the share mode spinner
|
||||||
|
// Eg. the audio and tap toggles in SysUI Recorder
|
||||||
|
@LayoutRes protected open fun getOptionsViewLayoutId(): Int? = null
|
||||||
|
|
||||||
|
private fun createOptionsView(@LayoutRes layoutId: Int?) {
|
||||||
|
if (layoutId == null) return
|
||||||
|
val stub = findViewById<View>(R.id.options_stub) as ViewStub
|
||||||
|
stub.layoutResource = layoutId
|
||||||
|
stub.inflate()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
/*
|
||||||
|
* 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.content.Context
|
||||||
|
import android.os.Bundle
|
||||||
|
import com.android.systemui.R
|
||||||
|
|
||||||
|
/** Dialog to select screen recording options */
|
||||||
|
class MediaProjectionPermissionDialog(
|
||||||
|
context: Context?,
|
||||||
|
private val onStartRecordingClicked: Runnable,
|
||||||
|
appName: String?
|
||||||
|
) : BaseScreenSharePermissionDialog(context, createOptionList(), appName) {
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
setDialogTitle(R.string.media_projection_permission_dialog_title)
|
||||||
|
setStartButtonText(R.string.media_projection_permission_dialog_continue)
|
||||||
|
setStartButtonOnClickListener {
|
||||||
|
// Note that it is important to run this callback before dismissing, so that the
|
||||||
|
// callback can disable the dialog exit animation if it wants to.
|
||||||
|
onStartRecordingClicked.run()
|
||||||
|
dismiss()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private fun createOptionList(): List<ScreenShareOption> {
|
||||||
|
return listOf(
|
||||||
|
ScreenShareOption(
|
||||||
|
SINGLE_APP,
|
||||||
|
R.string.media_projection_permission_dialog_option_single_app,
|
||||||
|
R.string.media_projection_permission_dialog_warning_single_app
|
||||||
|
),
|
||||||
|
ScreenShareOption(
|
||||||
|
ENTIRE_SCREEN,
|
||||||
|
R.string.media_projection_permission_dialog_option_entire_screen,
|
||||||
|
R.string.media_projection_permission_dialog_warning_entire_screen
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
package com.android.systemui.screenrecord;
|
package com.android.systemui.screenrecord;
|
||||||
|
|
||||||
|
import android.app.Dialog;
|
||||||
import android.app.PendingIntent;
|
import android.app.PendingIntent;
|
||||||
import android.content.BroadcastReceiver;
|
import android.content.BroadcastReceiver;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
@@ -33,6 +34,7 @@ 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.flags.FeatureFlags;
|
||||||
|
import com.android.systemui.flags.Flags;
|
||||||
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.policy.CallbackController;
|
import com.android.systemui.statusbar.policy.CallbackController;
|
||||||
@@ -97,11 +99,15 @@ 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, FeatureFlags flags,
|
public Dialog createScreenRecordDialog(Context context, FeatureFlags flags,
|
||||||
DialogLaunchAnimator dialogLaunchAnimator, ActivityStarter activityStarter,
|
DialogLaunchAnimator dialogLaunchAnimator,
|
||||||
@Nullable Runnable onStartRecordingClicked) {
|
ActivityStarter activityStarter,
|
||||||
return new ScreenRecordDialog(context, this, activityStarter, mUserContextProvider,
|
@Nullable Runnable onStartRecordingClicked) {
|
||||||
flags, dialogLaunchAnimator, onStartRecordingClicked);
|
return flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING)
|
||||||
|
? new ScreenRecordPermissionDialog(context, this, activityStarter,
|
||||||
|
dialogLaunchAnimator, mUserContextProvider, onStartRecordingClicked)
|
||||||
|
: new ScreenRecordDialog(context, this, activityStarter,
|
||||||
|
mUserContextProvider, flags, dialogLaunchAnimator, onStartRecordingClicked);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -0,0 +1,173 @@
|
|||||||
|
/*
|
||||||
|
* 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.app.Activity
|
||||||
|
import android.app.PendingIntent
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.os.Handler
|
||||||
|
import android.os.Looper
|
||||||
|
import android.os.ResultReceiver
|
||||||
|
import android.view.View
|
||||||
|
import android.widget.AdapterView
|
||||||
|
import android.widget.AdapterView.OnItemClickListener
|
||||||
|
import android.widget.ArrayAdapter
|
||||||
|
import android.widget.Spinner
|
||||||
|
import android.widget.Switch
|
||||||
|
import androidx.annotation.LayoutRes
|
||||||
|
import com.android.systemui.R
|
||||||
|
import com.android.systemui.animation.DialogLaunchAnimator
|
||||||
|
import com.android.systemui.media.MediaProjectionAppSelectorActivity
|
||||||
|
import com.android.systemui.media.MediaProjectionCaptureTarget
|
||||||
|
import com.android.systemui.plugins.ActivityStarter
|
||||||
|
import com.android.systemui.settings.UserContextProvider
|
||||||
|
|
||||||
|
/** Dialog to select screen recording options */
|
||||||
|
class ScreenRecordPermissionDialog(
|
||||||
|
context: Context?,
|
||||||
|
private val controller: RecordingController,
|
||||||
|
private val activityStarter: ActivityStarter,
|
||||||
|
private val dialogLaunchAnimator: DialogLaunchAnimator,
|
||||||
|
private val userContextProvider: UserContextProvider,
|
||||||
|
private val onStartRecordingClicked: Runnable?
|
||||||
|
) : BaseScreenSharePermissionDialog(context, createOptionList(), null) {
|
||||||
|
private lateinit var tapsSwitch: Switch
|
||||||
|
private lateinit var audioSwitch: Switch
|
||||||
|
private lateinit var options: Spinner
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
setDialogTitle(R.string.screenrecord_start_label)
|
||||||
|
setStartButtonText(R.string.screenrecord_start_recording)
|
||||||
|
setStartButtonOnClickListener { v: View? ->
|
||||||
|
onStartRecordingClicked?.run()
|
||||||
|
if (selectedScreenShareOption.mode == ENTIRE_SCREEN) {
|
||||||
|
requestScreenCapture(/* captureTarget= */ null)
|
||||||
|
}
|
||||||
|
if (selectedScreenShareOption.mode == SINGLE_APP) {
|
||||||
|
val intent = Intent(context, MediaProjectionAppSelectorActivity::class.java)
|
||||||
|
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(
|
||||||
|
MediaProjectionAppSelectorActivity.EXTRA_CAPTURE_REGION_RESULT_RECEIVER,
|
||||||
|
CaptureTargetResultReceiver()
|
||||||
|
)
|
||||||
|
val animationController = dialogLaunchAnimator.createActivityLaunchController(v!!)
|
||||||
|
if (animationController == null) {
|
||||||
|
dismiss()
|
||||||
|
}
|
||||||
|
activityStarter.startActivity(intent, /* dismissShade= */ true, animationController)
|
||||||
|
}
|
||||||
|
dismiss()
|
||||||
|
}
|
||||||
|
initRecordOptionsView()
|
||||||
|
}
|
||||||
|
|
||||||
|
@LayoutRes override fun getOptionsViewLayoutId(): Int = R.layout.screen_record_options
|
||||||
|
|
||||||
|
private fun initRecordOptionsView() {
|
||||||
|
audioSwitch = findViewById(R.id.screenrecord_audio_switch)
|
||||||
|
tapsSwitch = findViewById(R.id.screenrecord_taps_switch)
|
||||||
|
options = findViewById(R.id.screen_recording_options)
|
||||||
|
val a: ArrayAdapter<*> =
|
||||||
|
ScreenRecordingAdapter(context, android.R.layout.simple_spinner_dropdown_item, MODES)
|
||||||
|
a.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
|
||||||
|
options.adapter = a
|
||||||
|
options.setOnItemClickListenerInt(
|
||||||
|
OnItemClickListener { _: AdapterView<*>?, _: View?, _: Int, _: Long ->
|
||||||
|
audioSwitch.isChecked = true
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 fun requestScreenCapture(captureTarget: MediaProjectionCaptureTarget?) {
|
||||||
|
val userContext = userContextProvider.userContext
|
||||||
|
val showTaps = tapsSwitch.isChecked
|
||||||
|
val audioMode =
|
||||||
|
if (audioSwitch.isChecked) options.selectedItem as ScreenRecordingAudioSource
|
||||||
|
else ScreenRecordingAudioSource.NONE
|
||||||
|
val startIntent =
|
||||||
|
PendingIntent.getForegroundService(
|
||||||
|
userContext,
|
||||||
|
RecordingService.REQUEST_CODE,
|
||||||
|
RecordingService.getStartIntent(
|
||||||
|
userContext,
|
||||||
|
Activity.RESULT_OK,
|
||||||
|
audioMode.ordinal,
|
||||||
|
showTaps,
|
||||||
|
captureTarget
|
||||||
|
),
|
||||||
|
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
||||||
|
)
|
||||||
|
val stopIntent =
|
||||||
|
PendingIntent.getService(
|
||||||
|
userContext,
|
||||||
|
RecordingService.REQUEST_CODE,
|
||||||
|
RecordingService.getStopIntent(userContext),
|
||||||
|
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
||||||
|
)
|
||||||
|
controller.startCountdown(DELAY_MS, INTERVAL_MS, startIntent, stopIntent)
|
||||||
|
}
|
||||||
|
|
||||||
|
private inner class CaptureTargetResultReceiver() :
|
||||||
|
ResultReceiver(Handler(Looper.getMainLooper())) {
|
||||||
|
override fun onReceiveResult(resultCode: Int, resultData: Bundle) {
|
||||||
|
if (resultCode == Activity.RESULT_OK) {
|
||||||
|
val captureTarget =
|
||||||
|
resultData.getParcelable(
|
||||||
|
MediaProjectionAppSelectorActivity.KEY_CAPTURE_TARGET,
|
||||||
|
MediaProjectionCaptureTarget::class.java
|
||||||
|
)
|
||||||
|
|
||||||
|
// Start recording of the selected target
|
||||||
|
requestScreenCapture(captureTarget)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val MODES =
|
||||||
|
listOf(
|
||||||
|
ScreenRecordingAudioSource.INTERNAL,
|
||||||
|
ScreenRecordingAudioSource.MIC,
|
||||||
|
ScreenRecordingAudioSource.MIC_AND_INTERNAL
|
||||||
|
)
|
||||||
|
private const val DELAY_MS: Long = 3000
|
||||||
|
private const val INTERVAL_MS: Long = 1000
|
||||||
|
private fun createOptionList(): List<ScreenShareOption> {
|
||||||
|
return listOf(
|
||||||
|
ScreenShareOption(
|
||||||
|
SINGLE_APP,
|
||||||
|
R.string.screenrecord_option_single_app,
|
||||||
|
R.string.screenrecord_warning_single_app
|
||||||
|
),
|
||||||
|
ScreenShareOption(
|
||||||
|
ENTIRE_SCREEN,
|
||||||
|
R.string.screenrecord_option_entire_screen,
|
||||||
|
R.string.screenrecord_warning_entire_screen
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
/*
|
||||||
|
* 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 androidx.annotation.IntDef
|
||||||
|
import androidx.annotation.StringRes
|
||||||
|
import kotlin.annotation.Retention
|
||||||
|
|
||||||
|
@Retention(AnnotationRetention.SOURCE)
|
||||||
|
@IntDef(SINGLE_APP, ENTIRE_SCREEN)
|
||||||
|
annotation class ScreenShareMode
|
||||||
|
|
||||||
|
const val SINGLE_APP = 0
|
||||||
|
const val ENTIRE_SCREEN = 1
|
||||||
|
|
||||||
|
class ScreenShareOption(
|
||||||
|
@ScreenShareMode val mode: Int,
|
||||||
|
@StringRes val spinnerText: Int,
|
||||||
|
@StringRes val warningText: Int
|
||||||
|
)
|
||||||
Reference in New Issue
Block a user