Skip to content
Open
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
4 changes: 4 additions & 0 deletions packages/camera/camera_android_camerax/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 0.6.24+1

* Fix crash in `DeviceOrientationManager` caused by `UnsupportedOperationException` when `getDisplay()` is called on a null or destroyed Activity during rotation.

## 0.6.24

* Change plugin to assume mp4 format for capture videos.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.content.res.Configuration;
import android.util.Log;
import android.view.Display;
import android.view.OrientationEventListener;
import android.view.Surface;
Expand Down Expand Up @@ -48,8 +49,8 @@ Context getContext() {
* <p>When orientation information is updated, the callback method of the {@link
* DeviceOrientationManagerProxyApi} is called with the new orientation.
*/
@SuppressLint(
"UnprotectedReceiver") // orientationIntentFilter only listens to protected broadcast
@SuppressLint("UnprotectedReceiver")
// orientationIntentFilter only listens to protected broadcast
public void start() {
stop();

Expand Down Expand Up @@ -182,7 +183,19 @@ PlatformChannel.DeviceOrientation getUiOrientation() {
* Surface.ROTATION_270}
*/
int getDefaultRotation() {
return getDisplay().getRotation();
Display display = getDisplay();

if (display == null) {
// The Activity is not available (null or destroyed), which can happen briefly
// during configuration changes or due to race conditions. Returning ROTATION_0 ensures safe
// fallback and prevents crashes until a valid Activity is attached again.
Log.w(
"DeviceOrientationManager",
"Cannot get display: Activity may be null (destroyed or not yet attached) due to a race condition.");
return Surface.ROTATION_0;
}

return display.getRotation();
}

/**
Expand All @@ -194,6 +207,7 @@ int getDefaultRotation() {
* @return An instance of the Android {@link android.view.Display}.
*/
@VisibleForTesting
@Nullable
Display getDisplay() {
return api.getPigeonRegistrar().getDisplay();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -144,10 +144,15 @@ long getDefaultClearFinalizedWeakReferencesInterval() {
"deprecation") // getSystemService was the way of getting the default display prior to API 30
@Nullable
Display getDisplay() {
Activity activity = getActivity();
if (activity == null || activity.isDestroyed()) {
return null;
}

if (sdkIsAtLeast(Build.VERSION_CODES.R)) {
return getContext().getDisplay();
return activity.getDisplay();
} else {
return ((WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE))
return ((WindowManager) activity.getSystemService(Context.WINDOW_SERVICE))
.getDefaultDisplay();
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// Copyright 2013 The Flutter Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package android.util;

import android.app.Activity;
import android.view.WindowManager;

/**
* Fake Activity class used only for JVM unit tests. It avoids dependency on the real Android
* runtime and allows manual control of lifecycle states (isDestroyed, isFinishing) and the
* WindowManager instance.
*/
public class FakeActivity extends Activity {
private boolean destroyed;
private boolean finishing;
private WindowManager windowManager;

public void setDestroyed(boolean destroyed) {
this.destroyed = destroyed;
}

public void setFinishing(boolean finishing) {
this.finishing = finishing;
}

public void setWindowManager(WindowManager windowManager) {
this.windowManager = windowManager;
}

@Override
public boolean isDestroyed() {
return destroyed;
}

@Override
public boolean isFinishing() {
return finishing;
}

@Override
public WindowManager getWindowManager() {
return windowManager;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,11 @@
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.util.FakeActivity;
import android.view.Display;
import android.view.OrientationEventListener;
import android.view.Surface;
import android.view.WindowManager;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import io.flutter.embedding.engine.systemchannels.PlatformChannel.DeviceOrientation;
Expand Down Expand Up @@ -199,4 +201,40 @@ public void getDisplayTest() {

assertEquals(mockDisplay, display);
}

@Test
public void getDisplay_shouldReturnNull_whenActivityDestroyed() {
final DeviceOrientationManager deviceOrientationManager = createManager(true, false);
assertNull(deviceOrientationManager.getDisplay());
assertEquals(deviceOrientationManager.getDefaultRotation(), Surface.ROTATION_0);
}

@SuppressWarnings("deprecation")
private DeviceOrientationManager createManager(boolean destroyed, boolean finishing) {
FakeActivity activity = new FakeActivity();
activity.setDestroyed(destroyed);
activity.setFinishing(finishing);

WindowManager windowManager = mock(WindowManager.class);
when(windowManager.getDefaultDisplay()).thenReturn(mock(Display.class));
activity.setWindowManager(windowManager);

TestProxyApiRegistrar proxy =
new TestProxyApiRegistrar() {
@NonNull
@Override
public Context getContext() {
return activity;
}

@Nullable
@Override
public Activity getActivity() {
return activity;
}
};
when(mockApi.getPigeonRegistrar()).thenReturn(proxy);

return new DeviceOrientationManager(mockApi);
}
}
2 changes: 1 addition & 1 deletion packages/camera/camera_android_camerax/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: camera_android_camerax
description: Android implementation of the camera plugin using the CameraX library.
repository: https://github.com/flutter/packages/tree/main/packages/camera/camera_android_camerax
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+camera%22
version: 0.6.24
version: 0.6.24+1

environment:
sdk: ^3.9.0
Expand Down