-
Notifications
You must be signed in to change notification settings - Fork 912
Support element call widget (PSG-627) #6616
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 15 commits
125135c
61f05e7
b3a8052
85aba89
a0eb2e7
f5ec7a3
fd3b082
5c55263
9578350
85b5713
7dfe526
0d4697b
da780ac
81f3e4a
b389117
d631c70
242c14a
23a25cf
792fca8
5c253bb
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| Support element call widget |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -19,6 +19,7 @@ package im.vector.app.core.utils | |
| import android.Manifest | ||
| import android.app.Activity | ||
| import android.content.pm.PackageManager | ||
| import android.webkit.PermissionRequest | ||
| import androidx.activity.ComponentActivity | ||
| import androidx.activity.result.ActivityResultLauncher | ||
| import androidx.activity.result.contract.ActivityResultContracts | ||
|
|
@@ -137,6 +138,32 @@ fun checkPermissions( | |
| } | ||
| } | ||
|
|
||
| /** | ||
| * Checks if required WebView permissions are already granted system level. | ||
| * @param activity the calling Activity that is requesting the permissions (or fragment parent) | ||
| * @param request WebView permission request of onPermissionRequest function | ||
| * @return true if WebView permissions are already granted, false otherwise | ||
| */ | ||
| fun checkWebViewPermissions(activity: Activity, request: PermissionRequest): Boolean { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe we could extract this method into a dedicated use case such as We could also add unit tests on it as bonus.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good idea, done. |
||
| return request.resources.all { | ||
| when (it) { | ||
| PermissionRequest.RESOURCE_AUDIO_CAPTURE -> { | ||
| PERMISSIONS_FOR_AUDIO_IP_CALL.all { permission -> | ||
| ContextCompat.checkSelfPermission(activity.applicationContext, permission) == PackageManager.PERMISSION_GRANTED | ||
| } | ||
| } | ||
| PermissionRequest.RESOURCE_VIDEO_CAPTURE -> { | ||
| PERMISSIONS_FOR_VIDEO_IP_CALL.all { permission -> | ||
| ContextCompat.checkSelfPermission(activity.applicationContext, permission) == PackageManager.PERMISSION_GRANTED | ||
| } | ||
| } | ||
| else -> { | ||
| false | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * To be call after the permission request. | ||
| * | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -26,4 +26,5 @@ sealed class WidgetAction : VectorViewModelAction { | |
| object DeleteWidget : WidgetAction() | ||
| object RevokeWidget : WidgetAction() | ||
| object OnTermsReviewed : WidgetAction() | ||
| object HangupElementCall : WidgetAction() | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could we rename the action
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Renamed. |
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -17,8 +17,19 @@ | |
| package im.vector.app.features.widgets | ||
|
|
||
| import android.app.Activity | ||
| import android.app.PendingIntent | ||
| import android.app.PictureInPictureParams | ||
| import android.app.RemoteAction | ||
| import android.content.BroadcastReceiver | ||
| import android.content.Context | ||
| import android.content.Intent | ||
| import android.content.IntentFilter | ||
| import android.graphics.drawable.Icon | ||
| import android.os.Build | ||
| import android.util.Rational | ||
| import androidx.annotation.RequiresApi | ||
| import androidx.core.app.PictureInPictureModeChangedInfo | ||
| import androidx.core.util.Consumer | ||
| import androidx.core.view.isVisible | ||
| import com.airbnb.mvrx.Mavericks | ||
| import com.airbnb.mvrx.viewModel | ||
|
|
@@ -40,6 +51,10 @@ class WidgetActivity : VectorBaseActivity<ActivityWidgetBinding>() { | |
| private const val WIDGET_FRAGMENT_TAG = "WIDGET_FRAGMENT_TAG" | ||
| private const val WIDGET_PERMISSION_FRAGMENT_TAG = "WIDGET_PERMISSION_FRAGMENT_TAG" | ||
| private const val EXTRA_RESULT = "EXTRA_RESULT" | ||
| private const val REQUEST_CODE_HANGUP = 1 | ||
| private const val ACTION_MEDIA_CONTROL = "MEDIA_CONTROL" | ||
| private const val EXTRA_CONTROL_TYPE = "EXTRA_CONTROL_TYPE" | ||
| private const val CONTROL_TYPE_HANGUP = 2 | ||
|
|
||
| fun newIntent(context: Context, args: WidgetArgs): Intent { | ||
| return Intent(context, WidgetActivity::class.java).apply { | ||
|
|
@@ -82,29 +97,37 @@ class WidgetActivity : VectorBaseActivity<ActivityWidgetBinding>() { | |
| } | ||
| } | ||
|
|
||
| permissionViewModel.observeViewEvents { | ||
| when (it) { | ||
| is RoomWidgetPermissionViewEvents.Close -> finish() | ||
| // Trust element call widget by default | ||
| if (widgetArgs.kind == WidgetKind.ELEMENT_CALL) { | ||
| if (supportFragmentManager.findFragmentByTag(WIDGET_FRAGMENT_TAG) == null) { | ||
| addOnPictureInPictureModeChangedListener(pictureInPictureModeChangedInfoConsumer) | ||
| addFragment(views.fragmentContainer, WidgetFragment::class.java, widgetArgs, WIDGET_FRAGMENT_TAG) | ||
| } | ||
| } | ||
|
|
||
| viewModel.onEach(WidgetViewState::status) { ws -> | ||
| when (ws) { | ||
| WidgetStatus.UNKNOWN -> { | ||
| } else { | ||
| permissionViewModel.observeViewEvents { | ||
| when (it) { | ||
| is RoomWidgetPermissionViewEvents.Close -> finish() | ||
| } | ||
| WidgetStatus.WIDGET_NOT_ALLOWED -> { | ||
| val dFrag = supportFragmentManager.findFragmentByTag(WIDGET_PERMISSION_FRAGMENT_TAG) as? RoomWidgetPermissionBottomSheet | ||
| if (dFrag != null && dFrag.dialog?.isShowing == true && !dFrag.isRemoving) { | ||
| return@onEach | ||
| } else { | ||
| RoomWidgetPermissionBottomSheet | ||
| .newInstance(widgetArgs) | ||
| .show(supportFragmentManager, WIDGET_PERMISSION_FRAGMENT_TAG) | ||
| } | ||
|
|
||
| viewModel.onEach(WidgetViewState::status) { ws -> | ||
| when (ws) { | ||
| WidgetStatus.UNKNOWN -> { | ||
| } | ||
| } | ||
| WidgetStatus.WIDGET_ALLOWED -> { | ||
| if (supportFragmentManager.findFragmentByTag(WIDGET_FRAGMENT_TAG) == null) { | ||
| addFragment(views.fragmentContainer, WidgetFragment::class.java, widgetArgs, WIDGET_FRAGMENT_TAG) | ||
| WidgetStatus.WIDGET_NOT_ALLOWED -> { | ||
| val dFrag = supportFragmentManager.findFragmentByTag(WIDGET_PERMISSION_FRAGMENT_TAG) as? RoomWidgetPermissionBottomSheet | ||
| if (dFrag != null && dFrag.dialog?.isShowing == true && !dFrag.isRemoving) { | ||
| return@onEach | ||
| } else { | ||
| RoomWidgetPermissionBottomSheet | ||
| .newInstance(widgetArgs) | ||
| .show(supportFragmentManager, WIDGET_PERMISSION_FRAGMENT_TAG) | ||
| } | ||
| } | ||
| WidgetStatus.WIDGET_ALLOWED -> { | ||
| if (supportFragmentManager.findFragmentByTag(WIDGET_FRAGMENT_TAG) == null) { | ||
| addFragment(views.fragmentContainer, WidgetFragment::class.java, widgetArgs, WIDGET_FRAGMENT_TAG) | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
@@ -119,6 +142,64 @@ class WidgetActivity : VectorBaseActivity<ActivityWidgetBinding>() { | |
| } | ||
| } | ||
|
|
||
| override fun onUserLeaveHint() { | ||
| super.onUserLeaveHint() | ||
| val widgetArgs: WidgetArgs? = intent?.extras?.getParcelable(Mavericks.KEY_ARG) | ||
| if (widgetArgs?.kind == WidgetKind.ELEMENT_CALL) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I am wondering if we should move This way we could easily add picture in picture for other widget in the future.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Refactored. |
||
| enterPictureInPicture() | ||
| } | ||
| } | ||
|
|
||
| override fun onDestroy() { | ||
| removeOnPictureInPictureModeChangedListener(pictureInPictureModeChangedInfoConsumer) | ||
| super.onDestroy() | ||
| } | ||
|
|
||
| private fun enterPictureInPicture() { | ||
| if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { | ||
| createElementCallPipParams()?.let { | ||
| enterPictureInPictureMode(it) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| @RequiresApi(Build.VERSION_CODES.O) | ||
| private fun createElementCallPipParams(): PictureInPictureParams? { | ||
| val actions = mutableListOf<RemoteAction>() | ||
| val intent = Intent(ACTION_MEDIA_CONTROL).putExtra(EXTRA_CONTROL_TYPE, CONTROL_TYPE_HANGUP) | ||
| val pendingIntent = PendingIntent.getBroadcast(this, REQUEST_CODE_HANGUP, intent, 0) | ||
| val icon = Icon.createWithResource(this, R.drawable.ic_call_hangup) | ||
| actions.add(RemoteAction(icon, getString(R.string.call_notification_hangup), getString(R.string.call_notification_hangup), pendingIntent)) | ||
|
|
||
| val aspectRatio = Rational(resources.getDimensionPixelSize(R.dimen.call_pip_width), resources.getDimensionPixelSize(R.dimen.call_pip_height)) | ||
| return PictureInPictureParams.Builder() | ||
| .setAspectRatio(aspectRatio) | ||
| .setActions(actions) | ||
| .build() | ||
| } | ||
|
|
||
| private var hangupBroadcastReceiver: BroadcastReceiver? = null | ||
|
|
||
| private val pictureInPictureModeChangedInfoConsumer = Consumer<PictureInPictureModeChangedInfo> { | ||
| if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) return@Consumer | ||
|
|
||
| if (isInPictureInPictureMode) { | ||
| hangupBroadcastReceiver = object : BroadcastReceiver() { | ||
| override fun onReceive(context: Context?, intent: Intent?) { | ||
| if (intent?.action == ACTION_MEDIA_CONTROL) { | ||
| val controlType = intent.getIntExtra(EXTRA_CONTROL_TYPE, 0) | ||
| if (controlType == CONTROL_TYPE_HANGUP) { | ||
| viewModel.handle(WidgetAction.HangupElementCall) | ||
| } | ||
| } | ||
| } | ||
| } | ||
| registerReceiver(hangupBroadcastReceiver, IntentFilter(ACTION_MEDIA_CONTROL)) | ||
| } else { | ||
| unregisterReceiver(hangupBroadcastReceiver) | ||
| } | ||
| } | ||
|
|
||
| private fun handleClose(event: WidgetViewEvents.Close) { | ||
| if (event.content != null) { | ||
| val intent = createResultIntent(event.content) | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we have a trailing comma for the last item?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Of course, done.