Skip to content

Prepare SDK for upcoming Session Replay API changes on Android #2977

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

Draft
wants to merge 5 commits into
base: deps/flutter/scripts/update-android.sh/8.16.0
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# Changelog

## Unreleased

### Dependencies

- Bump Android SDK from v8.12.0 to v8.14.0 ([#2997](https://github.com/getsentry/sentry-dart/pull/2997))
- [changelog](https://github.com/getsentry/sentry-java/blob/main/CHANGELOG.md#8140)
- [diff](https://github.com/getsentry/sentry-java/compare/8.12.0...8.14.0)

## 9.1.0

### Features
Expand Down
2 changes: 1 addition & 1 deletion flutter/android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ android {
}

dependencies {
api 'io.sentry:sentry-android:8.12.0'
api 'io.sentry:sentry-android:8.14.0'
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"

// Required -- JUnit 4 framework
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -201,10 +201,10 @@ class SentryFlutter {
replayOptions.sessionSampleRate = (data["sessionSampleRate"] as? Number)?.toDouble()
replayOptions.onErrorSampleRate = (data["onErrorSampleRate"] as? Number)?.toDouble()

// Disable native tracking of orientation change (causes replay restart)
// Disable native tracking of window sizes
// because we don't have the new size from Flutter yet. Instead, we'll
// trigger onConfigurationChanged() manually in setReplayConfig().
replayOptions.setTrackOrientationChange(false)
replayOptions.isTrackConfiguration = false

@Suppress("UNCHECKED_CAST")
val tags = (data["tags"] as? Map<String, Any>) ?: mapOf()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,9 @@ package io.sentry.flutter
import android.annotation.SuppressLint
import android.app.Activity
import android.content.Context
import android.content.res.Configuration
import android.graphics.Point
import android.graphics.Rect
import android.os.Build
import android.os.Build.VERSION
import android.os.Build.VERSION_CODES
import android.os.Looper
import android.util.Log
import android.view.WindowManager
import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.embedding.engine.plugins.activity.ActivityAware
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding
Expand Down Expand Up @@ -50,18 +44,6 @@ class SentryFlutterPlugin :
private lateinit var context: Context
private lateinit var sentryFlutter: SentryFlutter

// Note: initial config because we don't yet have the numbers of the actual Flutter widget.
// See how SentryFlutterReplayRecorder.start() handles it. New settings will be set by setReplayConfig() method below.
private var replayConfig =
ScreenshotRecorderConfig(
recordingWidth = VIDEO_BLOCK_SIZE,
recordingHeight = VIDEO_BLOCK_SIZE,
scaleFactorX = 1.0f,
scaleFactorY = 1.0f,
frameRate = 1,
bitRate = 75000,
)

private var activity: WeakReference<Activity>? = null
private var framesTracker: ActivityFramesTracker? = null
private var pluginRegistrationTime: Long? = null
Expand Down Expand Up @@ -169,18 +151,6 @@ class SentryFlutterPlugin :
context.applicationContext,
dateProvider = CurrentDateProvider.getInstance(),
recorderProvider = { SentryFlutterReplayRecorder(channel, replay!!) },
recorderConfigProvider = {
Log.i(
"Sentry",
"Replay configuration requested. Returning: %dx%d at %d FPS, %d BPS".format(
replayConfig.recordingWidth,
replayConfig.recordingHeight,
replayConfig.frameRate,
replayConfig.bitRate,
),
)
replayConfig
},
replayCacheProvider = null,
)
replay!!.breadcrumbConverter = SentryFlutterReplayBreadcrumbConverter()
Expand Down Expand Up @@ -530,7 +500,8 @@ class SentryFlutterPlugin :

private const val NATIVE_CRASH_WAIT_TIME = 500L

@JvmStatic fun privateSentryGetReplayIntegration(): ReplayIntegration? = replay
@JvmStatic
fun privateSentryGetReplayIntegration(): ReplayIntegration? = replay

private fun crash() {
val exception = RuntimeException("FlutterSentry Native Integration: Sample RuntimeException")
Expand Down Expand Up @@ -572,8 +543,21 @@ class SentryFlutterPlugin :
// Since codec block size is 16, so we have to adjust the width and height to it,
// otherwise the codec might fail to configure on some devices, see
// https://cs.android.com/android/platform/superproject/+/master:frameworks/base/media/java/android/media/MediaCodecInfo.java;l=1999-2001
val windowWidth = call.argument("windowWidth") as? Double ?: 0.0
val windowHeight = call.argument("windowHeight") as? Double ?: 0.0

var width = call.argument("width") as? Double ?: 0.0
var height = call.argument("height") as? Double ?: 0.0

if (width == 0.0 || height == 0.0 || windowWidth == 0.0 || windowHeight == 0.0) {
result.error(
"5",
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if result.error must be processed, but I added it to prevent any division by zero

"Replay config is not valid: width: $width, height: $height, windowWidth: $windowWidth, windowHeight: $windowHeight",
null,
)
return
}

// First update the smaller dimension, as changing that will affect the screen ratio more.
if (width < height) {
val newWidth = width.adjustReplaySizeToBlockSize()
Expand All @@ -585,23 +569,12 @@ class SentryFlutterPlugin :
height = newHeight
}

val wm = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
val screenBounds =
if (VERSION.SDK_INT >= VERSION_CODES.R) {
wm.currentWindowMetrics.bounds
} else {
val screenBounds = Point()
@Suppress("DEPRECATION")
wm.defaultDisplay.getRealSize(screenBounds)
Rect(0, 0, screenBounds.x, screenBounds.y)
}

replayConfig =
val replayConfig =
ScreenshotRecorderConfig(
recordingWidth = width.roundToInt(),
recordingHeight = height.roundToInt(),
scaleFactorX = width.toFloat() / screenBounds.width().toFloat(),
scaleFactorY = height.toFloat() / screenBounds.height().toFloat(),
scaleFactorX = width.toFloat() / windowWidth.toFloat(),
scaleFactorY = height.toFloat() / windowHeight.toFloat(),
frameRate = call.argument("frameRate") as? Int ?: 0,
bitRate = call.argument("bitRate") as? Int ?: 0,
)
Expand All @@ -614,7 +587,7 @@ class SentryFlutterPlugin :
replayConfig.bitRate,
),
)
replay!!.onConfigurationChanged(Configuration())
replay?.onConfigurationChanged(replayConfig)
result.success("")
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,21 +12,12 @@ internal class SentryFlutterReplayRecorder(
private val channel: MethodChannel,
private val integration: ReplayIntegration,
) : Recorder {
override fun start(recorderConfig: ScreenshotRecorderConfig) {
// Ignore if this is the initial call before we actually got the configuration from Flutter.
// We'll get another call here when the configuration is changed according to the widget size.
if (recorderConfig.recordingHeight <= VIDEO_BLOCK_SIZE && recorderConfig.recordingWidth <= VIDEO_BLOCK_SIZE) {
return
}

override fun start() {
Handler(Looper.getMainLooper()).post {
try {
channel.invokeMethod(
"ReplayRecorder.start",
mapOf(
"width" to recorderConfig.recordingWidth,
"height" to recorderConfig.recordingHeight,
"frameRate" to recorderConfig.frameRate,
"replayId" to integration.getReplayId().toString(),
),
)
Expand All @@ -46,6 +37,33 @@ internal class SentryFlutterReplayRecorder(
}
}

override fun onConfigurationChanged(config: ScreenshotRecorderConfig) {
Handler(Looper.getMainLooper()).post {
try {
channel.invokeMethod(
"ReplayRecorder.onConfigurationChanged",
mapOf(
"width" to config.recordingWidth,
"height" to config.recordingHeight,
"frameRate" to config.frameRate,
),
)
} catch (ignored: Exception) {
Log.w("Sentry", "Failed to propagate configuration change to Flutter", ignored)
}
}
}

override fun reset() {
Handler(Looper.getMainLooper()).post {
try {
channel.invokeMethod("ReplayRecorder.reset", null)
} catch (ignored: Exception) {
Log.w("Sentry", "Failed to reset replay recorder", ignored)
}
}
}

override fun pause() {
Handler(Looper.getMainLooper()).post {
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,11 @@ class ScreenshotEventProcessor implements EventProcessor {

ScreenshotEventProcessor(this._options) {
final targetResolution = _options.screenshotQuality.targetResolution();
_recorder = ScreenshotRecorder(
ScreenshotRecorderConfig(
width: targetResolution,
height: targetResolution,
),
_options,
);
_recorder = ScreenshotRecorder(_options,
config: ScreenshotRecorderConfig(
width: targetResolution,
height: targetResolution,
));
_debouncer = Debouncer(
// ignore: invalid_use_of_internal_member
_options.clock,
Expand Down
9 changes: 5 additions & 4 deletions flutter/lib/src/native/cocoa/cocoa_replay_recorder.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,12 @@ class CocoaReplayRecorder {

CocoaReplayRecorder(this._options)
: _recorder = ReplayScreenshotRecorder(
ScreenshotRecorderConfig(
pixelRatio: _options.replay.quality.resolutionScalingFactor,
),
_options,
);
) {
_recorder.config = ScreenshotRecorderConfig(
pixelRatio: _options.replay.quality.resolutionScalingFactor,
);
}

Future<Map<String, int>?> captureScreenshot() async {
return _recorder.capture((screenshot) async {
Expand Down
6 changes: 2 additions & 4 deletions flutter/lib/src/native/java/android_replay_recorder.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@

import '../../../sentry_flutter.dart';
import '../../replay/scheduled_recorder.dart';
import '../../replay/scheduled_recorder_config.dart';
import '../../screenshot/screenshot.dart';
import 'binding.dart' as native;

Expand All @@ -18,11 +17,10 @@
_AndroidNativeReplayWorker? _worker;

@internal // visible for testing, used by SentryNativeJava
static AndroidReplayRecorder Function(
ScheduledScreenshotRecorderConfig, SentryFlutterOptions) factory =
static AndroidReplayRecorder Function(SentryFlutterOptions) factory =
AndroidReplayRecorder.new;

AndroidReplayRecorder(super.config, super.options) {
AndroidReplayRecorder(super.options) {

Check warning on line 23 in flutter/lib/src/native/java/android_replay_recorder.dart

View check run for this annotation

Codecov / codecov/patch

flutter/lib/src/native/java/android_replay_recorder.dart#L23

Added line #L23 was not covered by tests
super.callback = _addReplayScreenshot;
}

Expand Down
18 changes: 11 additions & 7 deletions flutter/lib/src/native/java/sentry_native_java.dart
Original file line number Diff line number Diff line change
Expand Up @@ -24,19 +24,20 @@
final replayId =
SentryId.fromId(call.arguments['replayId'] as String);

final config = ScheduledScreenshotRecorderConfig(
width: (call.arguments['width'] as num).toDouble(),
height: (call.arguments['height'] as num).toDouble(),
frameRate: call.arguments['frameRate'] as int);

_replayRecorder = AndroidReplayRecorder.factory(config, options);
_replayRecorder = AndroidReplayRecorder.factory(options);
await _replayRecorder!.start();

hub.configureScope((s) {
// ignore: invalid_use_of_internal_member
s.replayId = replayId;
});
break;
case 'ReplayRecorder.onConfigurationChanged':
final config = ScheduledScreenshotRecorderConfig(
width: (call.arguments['width'] as num).toDouble(),
height: (call.arguments['height'] as num).toDouble(),
frameRate: call.arguments['frameRate'] as int);

_replayRecorder?.onConfigurationChanged(config);
break;
case 'ReplayRecorder.stop':
hub.configureScope((s) {
Expand All @@ -55,6 +56,9 @@
case 'ReplayRecorder.resume':
await _replayRecorder?.resume();
break;
case 'ReplayRecorder.reset':

Check warning on line 59 in flutter/lib/src/native/java/sentry_native_java.dart

View check run for this annotation

Codecov / codecov/patch

flutter/lib/src/native/java/sentry_native_java.dart#L59

Added line #L59 was not covered by tests
// ignored
break;
default:
throw UnimplementedError('Method ${call.method} not implemented');
}
Expand Down
2 changes: 2 additions & 0 deletions flutter/lib/src/native/sentry_native_channel.dart
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,8 @@ class SentryNativeChannel
@override
FutureOr<void> setReplayConfig(ReplayConfig config) =>
channel.invokeMethod('setReplayConfig', {
'windowWidth': config.windowWidth,
'windowHeight': config.windowHeight,
'width': config.width,
'height': config.height,
'frameRate': config.frameRate,
Expand Down
2 changes: 2 additions & 0 deletions flutter/lib/src/replay/integration.dart
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ class ReplayIntegration extends Integration<SentryFlutterOptions> {
SentryScreenshotWidget.onBuild((status, prevStatus) {
if (status != prevStatus) {
_native.setReplayConfig(ReplayConfig(
windowWidth: status.size?.width ?? 0.0,
windowHeight: status.size?.height ?? 0.0,
width: replayOptions.quality.resolutionScalingFactor *
(status.size?.width ?? 0.0),
height: replayOptions.quality.resolutionScalingFactor *
Expand Down
6 changes: 6 additions & 0 deletions flutter/lib/src/replay/replay_config.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,19 @@ import 'scheduled_recorder_config.dart';
@immutable
@internal
class ReplayConfig extends ScheduledScreenshotRecorderConfig {
final double windowWidth;

final double windowHeight;

@override
double get width => super.width!;

@override
double get height => super.height!;

const ReplayConfig({
required this.windowWidth,
required this.windowHeight,
required double super.width,
required double super.height,
super.frameRate = 1,
Expand Down
2 changes: 1 addition & 1 deletion flutter/lib/src/replay/replay_recorder.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ var _instanceCounter = 0;

@internal
class ReplayScreenshotRecorder extends ScreenshotRecorder {
ReplayScreenshotRecorder(super.config, super.options)
ReplayScreenshotRecorder(super.options)
: super(
privacyOptions: options.privacy,
logName: 'ReplayRecorder #${++_instanceCounter}');
Expand Down
Loading
Loading