Skip to content
Open
Show file tree
Hide file tree
Changes from 6 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
5 changes: 5 additions & 0 deletions packages/camera/camera_android_camerax/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
## 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 @@ -182,7 +183,18 @@ PlatformChannel.DeviceOrientation getUiOrientation() {
* Surface.ROTATION_270}
*/
int getDefaultRotation() {
return getDisplay().getRotation();
Display display = getDisplay();

if (display == null) {
// The Activity is not available (null, finishing, or destroyed), which can happen briefly
// during configuration changes. 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 during rotation.");
return Surface.ROTATION_0;
}

return display.getRotation();
}

/**
Expand All @@ -194,6 +206,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() || activity.isFinishing()) {
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,47 @@ 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);
}

@Test
public void getDisplay_shouldReturnNull_whenActivityFinishing() {
final DeviceOrientationManager deviceOrientationManager = createManager(false, true);
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