[android-auto] Fix activity recreation when [dis]connecting to car

Signed-off-by: Andrei Shkrob <github@shkrob.dev>
This commit is contained in:
Andrei Shkrob
2025-09-20 17:28:21 +02:00
committed by x7z4w
parent 832273f928
commit 707415a788
12 changed files with 314 additions and 97 deletions

View File

@@ -246,7 +246,7 @@ public class MwmActivity extends BaseMwmFragmentActivity
ManageRouteBottomSheet mManageRouteBottomSheet; ManageRouteBottomSheet mManageRouteBottomSheet;
private boolean mRemoveDisplayListener = true; private boolean mRemoveDisplayListener = true;
private int mLastUiMode = Configuration.UI_MODE_TYPE_UNDEFINED; private static int mLastUiMode = Configuration.UI_MODE_TYPE_UNDEFINED;
public interface LeftAnimationTrackListener public interface LeftAnimationTrackListener
{ {
@@ -471,12 +471,10 @@ public class MwmActivity extends BaseMwmFragmentActivity
super.onConfigurationChanged(newConfig); super.onConfigurationChanged(newConfig);
final int newUiMode = newConfig.uiMode & Configuration.UI_MODE_TYPE_MASK; final int newUiMode = newConfig.uiMode & Configuration.UI_MODE_TYPE_MASK;
final boolean newUiModeIsCarConnected = newUiMode == Configuration.UI_MODE_TYPE_CAR; final boolean carModeChanged = (newUiMode | mLastUiMode & Configuration.UI_MODE_TYPE_CAR) != 0;
final boolean newUiModeIsCarDisconnected =
mLastUiMode == Configuration.UI_MODE_TYPE_CAR && newUiMode == Configuration.UI_MODE_TYPE_NORMAL;
mLastUiMode = newUiMode; mLastUiMode = newUiMode;
if (newUiModeIsCarConnected || newUiModeIsCarDisconnected) if (carModeChanged)
return; return;
makeNavigationBarTransparentInLightMode(); makeNavigationBarTransparentInLightMode();

View File

@@ -13,7 +13,7 @@ import androidx.lifecycle.LifecycleOwner;
import app.organicmaps.MwmApplication; import app.organicmaps.MwmApplication;
import app.organicmaps.R; import app.organicmaps.R;
import app.organicmaps.car.renderer.Renderer; import app.organicmaps.car.renderer.Renderer;
import app.organicmaps.car.renderer.SurfaceRendererLegacy; import app.organicmaps.car.renderer.RendererFactory;
import app.organicmaps.car.screens.ErrorScreen; import app.organicmaps.car.screens.ErrorScreen;
import app.organicmaps.car.screens.MapPlaceholderScreen; import app.organicmaps.car.screens.MapPlaceholderScreen;
import app.organicmaps.car.screens.MapScreen; import app.organicmaps.car.screens.MapScreen;
@@ -113,10 +113,11 @@ public final class CarAppSession extends Session implements DefaultLifecycleObse
public void onCreate(@NonNull LifecycleOwner owner) public void onCreate(@NonNull LifecycleOwner owner)
{ {
Logger.d(TAG); Logger.d(TAG);
mSurfaceRenderer = new SurfaceRendererLegacy(getCarContext(), getLifecycle());
mSensorsManager = new CarSensorsManager(getCarContext()); mSensorsManager = new CarSensorsManager(getCarContext());
mDisplayManager = MwmApplication.from(getCarContext()).getDisplayManager(); mDisplayManager = MwmApplication.from(getCarContext()).getDisplayManager();
mDisplayManager.addListener(DisplayType.Car, this); mDisplayManager.addListener(DisplayType.Car, this);
mSurfaceRenderer = RendererFactory.create(getCarContext(), mDisplayManager,
MwmApplication.from(getCarContext()).getLocationHelper(), this);
init(); init();
} }

View File

@@ -19,6 +19,10 @@ public abstract class Renderer implements DefaultLifecycleObserver
{ {
private static final String TAG = Renderer.class.getSimpleName(); private static final String TAG = Renderer.class.getSimpleName();
private SurfaceCallback mSurfaceCallback;
private boolean mIsRunning;
@NonNull @NonNull
protected final CarContext mCarContext; protected final CarContext mCarContext;
@@ -28,9 +32,8 @@ public abstract class Renderer implements DefaultLifecycleObserver
@NonNull @NonNull
protected final LocationHelper mLocationHelper; protected final LocationHelper mLocationHelper;
private boolean mIsRunning; @NonNull
protected final LifecycleOwner mLifecycleOwner;
private SurfaceCallback mSurfaceCallback;
@NonNull @NonNull
private final MapRenderingListener mMapRenderingListener = new MapRenderingListener() { private final MapRenderingListener mMapRenderingListener = new MapRenderingListener() {
@@ -42,14 +45,15 @@ public abstract class Renderer implements DefaultLifecycleObserver
}; };
public Renderer(@NonNull CarContext carContext, @NonNull DisplayManager displayManager, public Renderer(@NonNull CarContext carContext, @NonNull DisplayManager displayManager,
@NonNull LocationHelper locationHelper, @NonNull Lifecycle lifecycle) @NonNull LocationHelper locationHelper, @NonNull LifecycleOwner lifecycleOwner)
{ {
Logger.d(TAG, "SurfaceRenderer()"); Logger.d(TAG, "SurfaceRenderer()");
mIsRunning = true;
mCarContext = carContext; mCarContext = carContext;
mDisplayManager = displayManager; mDisplayManager = displayManager;
mLocationHelper = locationHelper; mLocationHelper = locationHelper;
mIsRunning = true; mLifecycleOwner = lifecycleOwner;
lifecycle.addObserver(this); mLifecycleOwner.getLifecycle().addObserver(this);
} }
protected void setSurfaceCallback(@NonNull SurfaceCallback surfaceCallback) protected void setSurfaceCallback(@NonNull SurfaceCallback surfaceCallback)

View File

@@ -0,0 +1,20 @@
package app.organicmaps.car.renderer;
import androidx.annotation.NonNull;
import androidx.car.app.CarContext;
import androidx.lifecycle.LifecycleOwner;
import app.organicmaps.sdk.display.DisplayManager;
import app.organicmaps.sdk.location.LocationHelper;
public final class RendererFactory
{
@NonNull
public static Renderer create(@NonNull CarContext carContext, @NonNull DisplayManager displayManager,
@NonNull LocationHelper locationHelper, @NonNull LifecycleOwner lifecycleOwner)
{
if (android.os.Build.VERSION.SDK_INT >= 23)
return new SurfaceRenderer(carContext, displayManager, locationHelper, lifecycleOwner);
else
return new SurfaceRendererLegacy(carContext, displayManager, locationHelper, lifecycleOwner);
}
}

View File

@@ -0,0 +1,86 @@
package app.organicmaps.car.renderer;
import android.app.Presentation;
import android.hardware.display.DisplayManager;
import android.hardware.display.VirtualDisplay;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.widget.FrameLayout;
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import androidx.car.app.CarContext;
import androidx.car.app.SurfaceContainer;
import app.organicmaps.sdk.MapController;
import app.organicmaps.sdk.util.log.Logger;
@RequiresApi(23)
class SurfaceCallback extends SurfaceCallbackBase
{
private static final String TAG = SurfaceCallback.class.getSimpleName();
private static final String VIRTUAL_DISPLAY_NAME = "OM_Android_Auto_Display";
private static final int VIRTUAL_DISPLAY_FLAGS =
DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION | DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY;
@NonNull
private final MapController mMapController;
private VirtualDisplay mVirtualDisplay;
private Presentation mPresentation;
public SurfaceCallback(@NonNull CarContext carContext, @NonNull MapController mapController)
{
super(carContext);
mMapController = mapController;
}
@Override
public void onSurfaceAvailable(@NonNull SurfaceContainer surfaceContainer)
{
Logger.d(TAG, "Surface available " + surfaceContainer);
mVirtualDisplay =
mCarContext.getSystemService(DisplayManager.class)
.createVirtualDisplay(VIRTUAL_DISPLAY_NAME, surfaceContainer.getWidth(), surfaceContainer.getHeight(),
surfaceContainer.getDpi(), surfaceContainer.getSurface(), VIRTUAL_DISPLAY_FLAGS);
mPresentation = new Presentation(mCarContext, mVirtualDisplay.getDisplay());
mPresentation.setContentView(prepareViewForPresentation(mMapController.getView()));
mPresentation.show();
}
@Override
public void onSurfaceDestroyed(@NonNull SurfaceContainer surfaceContainer)
{
Logger.d(TAG, "Surface destroyed");
mPresentation.dismiss();
mVirtualDisplay.release();
}
void stopPresenting()
{
if (mPresentation != null)
mPresentation.dismiss();
}
void startPresenting()
{
if (mPresentation != null)
mPresentation.show();
}
@NonNull
private View prepareViewForPresentation(@NonNull View view)
{
final ViewParent parent = view.getParent();
if (parent instanceof ViewGroup)
((ViewGroup) parent).removeView(view);
final FrameLayout container = new FrameLayout(mCarContext);
container.addView(
view, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
return container;
}
}

View File

@@ -0,0 +1,94 @@
package app.organicmaps.car.renderer;
import android.graphics.Rect;
import androidx.annotation.NonNull;
import androidx.car.app.CarContext;
import androidx.car.app.SurfaceCallback;
import app.organicmaps.sdk.Framework;
import app.organicmaps.sdk.Map;
import app.organicmaps.sdk.util.concurrency.UiThread;
import app.organicmaps.sdk.util.log.Logger;
abstract class SurfaceCallbackBase implements SurfaceCallback
{
private static final String TAG = SurfaceCallbackBase.class.getSimpleName();
@NonNull
protected final CarContext mCarContext;
@NonNull
protected Rect mVisibleArea = new Rect();
public SurfaceCallbackBase(@NonNull CarContext carContext)
{
mCarContext = carContext;
}
@Override
public void onVisibleAreaChanged(@NonNull Rect visibleArea)
{
Logger.d(TAG, "Visible area changed. visibleArea: " + visibleArea);
mVisibleArea = visibleArea;
if (!mVisibleArea.isEmpty())
UiThread.runLater(()
-> Framework.nativeSetVisibleRect(mVisibleArea.left, mVisibleArea.top, mVisibleArea.right,
mVisibleArea.bottom));
}
@Override
public void onStableAreaChanged(@NonNull Rect stableArea)
{
Logger.d(TAG, "Stable area changed. stableArea: " + stableArea);
if (!mVisibleArea.isEmpty())
UiThread.runLater(()
-> Framework.nativeSetVisibleRect(mVisibleArea.left, mVisibleArea.top, mVisibleArea.right,
mVisibleArea.bottom));
else if (!stableArea.isEmpty())
UiThread.runLater(
() -> Framework.nativeSetVisibleRect(stableArea.left, stableArea.top, stableArea.right, stableArea.bottom));
}
@Override
public void onScroll(float distanceX, float distanceY)
{
Logger.d(TAG, "distanceX: " + distanceX + ", distanceY: " + distanceY);
Map.onScroll(distanceX, distanceY);
}
@Override
public void onFling(float velocityX, float velocityY)
{
Logger.d(TAG, "velocityX: " + velocityX + ", velocityY: " + velocityY);
// TODO: Implement fling in the native code.
}
@Override
public void onScale(float focusX, float focusY, float scaleFactor)
{
Logger.d(TAG, "focusX: " + focusX + ", focusY: " + focusY + ", scaleFactor: " + scaleFactor);
float x = focusX;
float y = focusY;
if (!mVisibleArea.isEmpty())
{
// If a focal point value is negative, use the center point of the visible area.
if (x < 0)
x = mVisibleArea.centerX();
if (y < 0)
y = mVisibleArea.centerY();
}
final boolean animated = Float.compare(scaleFactor, 2f) == 0;
Map.onScale(scaleFactor, x, y, animated);
}
@Override
public void onClick(float x, float y)
{
Logger.d(TAG, "x: " + x + ", y: " + y);
Map.onClick(x, y);
}
}

View File

@@ -5,20 +5,15 @@ import android.view.Surface;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.car.app.CarContext; import androidx.car.app.CarContext;
import androidx.car.app.SurfaceCallback;
import androidx.car.app.SurfaceContainer; import androidx.car.app.SurfaceContainer;
import app.organicmaps.sdk.Framework;
import app.organicmaps.sdk.Map; import app.organicmaps.sdk.Map;
import app.organicmaps.sdk.location.LocationHelper; import app.organicmaps.sdk.location.LocationHelper;
import app.organicmaps.sdk.util.log.Logger; import app.organicmaps.sdk.util.log.Logger;
public class SurfaceCallbackLegacy implements SurfaceCallback class SurfaceCallbackLegacy extends SurfaceCallbackBase
{ {
private static final String TAG = SurfaceCallbackLegacy.class.getSimpleName(); private static final String TAG = SurfaceCallbackLegacy.class.getSimpleName();
@NonNull
protected final CarContext mCarContext;
@NonNull @NonNull
private final Map mMap; private final Map mMap;
@@ -28,12 +23,9 @@ public class SurfaceCallbackLegacy implements SurfaceCallback
@Nullable @Nullable
private Surface mSurface = null; private Surface mSurface = null;
@NonNull
private Rect mVisibleArea = new Rect();
public SurfaceCallbackLegacy(@NonNull CarContext carContext, @NonNull Map map, @NonNull LocationHelper locationHelper) public SurfaceCallbackLegacy(@NonNull CarContext carContext, @NonNull Map map, @NonNull LocationHelper locationHelper)
{ {
mCarContext = carContext; super(carContext);
mMap = map; mMap = map;
mLocationHelper = locationHelper; mLocationHelper = locationHelper;
} }
@@ -65,67 +57,4 @@ public class SurfaceCallbackLegacy implements SurfaceCallback
} }
mMap.onSurfaceDestroyed(false); mMap.onSurfaceDestroyed(false);
} }
@Override
public void onVisibleAreaChanged(@NonNull Rect visibleArea)
{
Logger.d(TAG, "Visible area changed. visibleArea: " + visibleArea);
mVisibleArea = visibleArea;
if (!mVisibleArea.isEmpty())
Framework.nativeSetVisibleRect(mVisibleArea.left, mVisibleArea.top, mVisibleArea.right, mVisibleArea.bottom);
}
@Override
public void onStableAreaChanged(@NonNull Rect stableArea)
{
Logger.d(TAG, "Stable area changed. stableArea: " + stableArea);
if (!stableArea.isEmpty())
Framework.nativeSetVisibleRect(stableArea.left, stableArea.top, stableArea.right, stableArea.bottom);
else if (!mVisibleArea.isEmpty())
Framework.nativeSetVisibleRect(mVisibleArea.left, mVisibleArea.top, mVisibleArea.right, mVisibleArea.bottom);
}
@Override
public void onScroll(float distanceX, float distanceY)
{
Logger.d(TAG, "distanceX: " + distanceX + ", distanceY: " + distanceY);
mMap.onScroll(distanceX, distanceY);
}
@Override
public void onFling(float velocityX, float velocityY)
{
Logger.d(TAG, "velocityX: " + velocityX + ", velocityY: " + velocityY);
// TODO: Implement fling in the native code.
}
@Override
public void onScale(float focusX, float focusY, float scaleFactor)
{
Logger.d(TAG, "focusX: " + focusX + ", focusY: " + focusY + ", scaleFactor: " + scaleFactor);
float x = focusX;
float y = focusY;
if (!mVisibleArea.isEmpty())
{
// If a focal point value is negative, use the center point of the visible area.
if (x < 0)
x = mVisibleArea.centerX();
if (y < 0)
y = mVisibleArea.centerY();
}
final boolean animated = Float.compare(scaleFactor, 2f) == 0;
Map.onScale(scaleFactor, x, y, animated);
}
@Override
public void onClick(float x, float y)
{
Logger.d(TAG, "x: " + x + ", y: " + y);
Map.onClick(x, y);
}
} }

View File

@@ -0,0 +1,64 @@
package app.organicmaps.car.renderer;
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import androidx.car.app.CarContext;
import androidx.lifecycle.LifecycleOwner;
import app.organicmaps.sdk.MapController;
import app.organicmaps.sdk.MapView;
import app.organicmaps.sdk.display.DisplayManager;
import app.organicmaps.sdk.display.DisplayType;
import app.organicmaps.sdk.location.LocationHelper;
import app.organicmaps.sdk.util.log.Logger;
@RequiresApi(23)
class SurfaceRenderer extends Renderer
{
private static final String TAG = SurfaceRenderer.class.getSimpleName();
@NonNull
private final MapController mMapController;
@NonNull
private final SurfaceCallback mSurfaceCallback;
public SurfaceRenderer(@NonNull CarContext carContext, @NonNull DisplayManager displayManager,
@NonNull LocationHelper locationHelper, @NonNull LifecycleOwner lifecycleOwner)
{
super(carContext, displayManager, locationHelper, lifecycleOwner);
mMapController = new MapController(new MapView(carContext, DisplayType.Car), locationHelper,
getMapRenderingListener(), null, false);
mLifecycleOwner.getLifecycle().addObserver(mMapController);
mSurfaceCallback = new SurfaceCallback(mCarContext, mMapController);
setSurfaceCallback(mSurfaceCallback);
}
@Override
public void onResume(@NonNull LifecycleOwner owner)
{
Logger.d(TAG);
if (mDisplayManager.isCarDisplayUsed())
mMapController.updateMyPositionRoutingOffset(0);
}
@Override
public void enable()
{
super.enable();
mMapController.onStart(mLifecycleOwner);
mMapController.updateMyPositionRoutingOffset(0);
mSurfaceCallback.startPresenting();
}
@Override
public void disable()
{
super.disable();
mMapController.onPause(mLifecycleOwner);
mSurfaceCallback.stopPresenting();
mMapController.onStop(mLifecycleOwner);
}
}

View File

@@ -4,23 +4,23 @@ import static app.organicmaps.sdk.display.DisplayType.Car;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.car.app.CarContext; import androidx.car.app.CarContext;
import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.LifecycleOwner; import androidx.lifecycle.LifecycleOwner;
import app.organicmaps.MwmApplication;
import app.organicmaps.sdk.Map; import app.organicmaps.sdk.Map;
import app.organicmaps.sdk.display.DisplayManager;
import app.organicmaps.sdk.location.LocationHelper;
import app.organicmaps.sdk.util.log.Logger; import app.organicmaps.sdk.util.log.Logger;
public class SurfaceRendererLegacy extends Renderer class SurfaceRendererLegacy extends Renderer
{ {
private static final String TAG = SurfaceRendererLegacy.class.getSimpleName(); private static final String TAG = SurfaceRendererLegacy.class.getSimpleName();
@NonNull @NonNull
private final Map mMap = new Map(Car); private final Map mMap = new Map(Car);
public SurfaceRendererLegacy(@NonNull CarContext carContext, @NonNull Lifecycle lifecycle) public SurfaceRendererLegacy(@NonNull CarContext carContext, @NonNull DisplayManager displayManager,
@NonNull LocationHelper locationHelper, @NonNull LifecycleOwner lifecycleOwner)
{ {
super(carContext, MwmApplication.from(carContext).getDisplayManager(), super(carContext, displayManager, locationHelper, lifecycleOwner);
MwmApplication.from(carContext).getLocationHelper(), lifecycle);
setSurfaceCallback(new SurfaceCallbackLegacy(mCarContext, mMap, mLocationHelper)); setSurfaceCallback(new SurfaceCallbackLegacy(mCarContext, mMap, mLocationHelper));
mMap.setMapRenderingListener(getMapRenderingListener()); mMap.setMapRenderingListener(getMapRenderingListener());
} }

View File

@@ -280,7 +280,7 @@ public final class Map
return mSurfaceCreated; return mSurfaceCreated;
} }
public void onScroll(double distanceX, double distanceY) public static void onScroll(double distanceX, double distanceY)
{ {
Map.nativeOnScroll(distanceX, distanceY); Map.nativeOnScroll(distanceX, distanceY);
} }
@@ -329,6 +329,11 @@ public final class Map
nativeExecuteMapApiRequest(); nativeExecuteMapApiRequest();
} }
public DisplayType getDisplayType()
{
return mDisplayType;
}
private void setupWidgets(final Context context, int width, int height) private void setupWidgets(final Context context, int width, int height)
{ {
mHeight = height; mHeight = height;

View File

@@ -9,9 +9,13 @@ import app.organicmaps.sdk.util.log.Logger;
public class MapController implements DefaultLifecycleObserver public class MapController implements DefaultLifecycleObserver
{ {
private static final String TAG = MapController.class.getSimpleName(); private static final String TAG_PEFRIX = MapController.class.getSimpleName();
@NonNull
private final String TAG;
@NonNull
private final MapView mMapView; private final MapView mMapView;
@NonNull
private final Map mMap; private final Map mMap;
@Nullable @Nullable
@@ -19,7 +23,7 @@ public class MapController implements DefaultLifecycleObserver
public MapController(@NonNull MapView mapView, @NonNull LocationHelper locationHelper, public MapController(@NonNull MapView mapView, @NonNull LocationHelper locationHelper,
@NonNull MapRenderingListener mapRenderingListener, @NonNull MapRenderingListener mapRenderingListener,
@NonNull Map.CallbackUnsupported callbackUnsupported, boolean launchByDeepLink) @Nullable Map.CallbackUnsupported callbackUnsupported, boolean launchByDeepLink)
{ {
mMapView = mapView; mMapView = mapView;
mMap = mMapView.getMap(); mMap = mMapView.getMap();
@@ -27,6 +31,7 @@ public class MapController implements DefaultLifecycleObserver
mMap.setLocationHelper(locationHelper); mMap.setLocationHelper(locationHelper);
mMap.setMapRenderingListener(mapRenderingListener); mMap.setMapRenderingListener(mapRenderingListener);
mMap.setCallbackUnsupported(callbackUnsupported); mMap.setCallbackUnsupported(callbackUnsupported);
TAG = TAG_PEFRIX + "[" + mMap.getDisplayType() + "]";
} }
public MapView getView() public MapView getView()

View File

@@ -51,7 +51,12 @@ public class MapView extends SurfaceView
public MapView(Context context) public MapView(Context context)
{ {
this(context, null); this(context, null, 0);
}
public MapView(Context context, DisplayType displayType)
{
this(context, null, 0, 0, displayType);
} }
public MapView(Context context, AttributeSet attrs) public MapView(Context context, AttributeSet attrs)
@@ -65,9 +70,15 @@ public class MapView extends SurfaceView
} }
public MapView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) public MapView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)
{
this(context, attrs, defStyleAttr, defStyleRes, DisplayType.Device);
}
private MapView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes,
@NonNull DisplayType displayType)
{ {
super(context, attrs, defStyleAttr, defStyleRes); super(context, attrs, defStyleAttr, defStyleRes);
mMap = new Map(DisplayType.Device); mMap = new Map(displayType);
getHolder().addCallback(new SurfaceHolderCallback()); getHolder().addCallback(new SurfaceHolderCallback());
} }