/* * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.car.cluster; import android.content.Context; import android.graphics.Rect; import android.hardware.display.DisplayManager; import android.hardware.display.DisplayManager.DisplayListener; import android.hardware.display.VirtualDisplay; import android.os.Bundle; import android.os.Handler; import android.util.Log; import android.view.Display; import android.view.LayoutInflater; import android.view.Surface; import android.view.SurfaceHolder; import android.view.SurfaceHolder.Callback; import android.view.SurfaceView; import android.view.View; import android.view.ViewGroup; import android.widget.ProgressBar; import android.widget.TextView; import androidx.fragment.app.Fragment; import androidx.lifecycle.ViewModelProvider; import androidx.lifecycle.ViewModelProviders; public class NavigationFragment extends Fragment { private static final String TAG = "Cluster.NavFragment"; private SurfaceView mSurfaceView; private DisplayManager mDisplayManager; private Rect mUnobscuredBounds; private MainClusterActivity mMainClusterActivity; private ClusterViewModel mViewModel; private ProgressBar mProgressBar; private TextView mMessage; // Static because we want to keep alive this virtual display when navigating through // ViewPager (this fragment gets dynamically destroyed and created) private static VirtualDisplay mVirtualDisplay; private static int mRegisteredNavDisplayId = Display.INVALID_DISPLAY; private boolean mNavigationDisplayUpdatePending = false; public NavigationFragment() { // Required empty public constructor } private final DisplayListener mDisplayListener = new DisplayListener() { @Override public void onDisplayAdded(int displayId) { int navDisplayId = getVirtualDisplayId(); Log.i(TAG, "onDisplayAdded, displayId: " + displayId + ", navigation display id: " + navDisplayId); if (navDisplayId == displayId) { mRegisteredNavDisplayId = displayId; updateNavigationDisplay(); } } @Override public void onDisplayRemoved(int displayId) { if (mRegisteredNavDisplayId == displayId) { mRegisteredNavDisplayId = Display.INVALID_DISPLAY; updateNavigationDisplay(); } } @Override public void onDisplayChanged(int displayId) {} }; private void updateNavigationDisplay() { if (mMainClusterActivity == null) { // Not attached to the activity yet. Let's wait. mNavigationDisplayUpdatePending = true; return; } mNavigationDisplayUpdatePending = false; mMainClusterActivity.updateNavDisplay(new MainClusterActivity.VirtualDisplay( mRegisteredNavDisplayId, mUnobscuredBounds)); } @Override public void onAttach(Context context) { super.onAttach(context); mMainClusterActivity = (MainClusterActivity) context; if (mNavigationDisplayUpdatePending) { updateNavigationDisplay(); } } @Override public void onDetach() { mMainClusterActivity = null; super.onDetach(); } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { Log.i(TAG, "onCreateView"); ViewModelProvider provider = ViewModelProviders.of(requireActivity()); mViewModel = provider.get(ClusterViewModel.class); mDisplayManager = getActivity().getSystemService(DisplayManager.class); mDisplayManager.registerDisplayListener(mDisplayListener, new Handler()); // Inflate the layout for this fragment View root = inflater.inflate(R.layout.fragment_navigation, container, false); mSurfaceView = root.findViewById(R.id.nav_surface); mSurfaceView.getHolder().addCallback(new Callback() { @Override public void surfaceCreated(SurfaceHolder holder) { Log.i(TAG, "surfaceCreated, holder: " + holder); } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { Log.i(TAG, "surfaceChanged, holder: " + holder + ", size:" + width + "x" + height + ", format:" + format); // Create mock unobscured area to report to navigation activity. int obscuredWidth = (int) getResources() .getDimension(R.dimen.speedometer_overlap_width); int obscuredHeight = (int) getResources() .getDimension(R.dimen.navigation_gradient_height); mUnobscuredBounds = new Rect( obscuredWidth, /* left: size of gauge */ obscuredHeight, /* top: gradient */ width - obscuredWidth, /* right: size of the display - size of gauge */ height - obscuredHeight /* bottom: size of display - gradient */ ); if (mVirtualDisplay == null) { mVirtualDisplay = createVirtualDisplay(holder.getSurface(), width, height); } else { mVirtualDisplay.setSurface(holder.getSurface()); } } @Override public void surfaceDestroyed(SurfaceHolder holder) { Log.i(TAG, "surfaceDestroyed, holder: " + holder + ", detaching surface from" + " display, surface: " + holder.getSurface()); // detaching surface is similar to turning off the display mVirtualDisplay.setSurface(null); } }); mProgressBar = root.findViewById(R.id.progress_bar); mMessage = root.findViewById(R.id.message); mViewModel.getNavigationActivityState().observe(this, state -> { if (Log.isLoggable(TAG, Log.DEBUG)) { Log.d(TAG, "State: " + state); } mProgressBar.setVisibility(state == ClusterViewModel.NavigationActivityState.LOADING ? View.VISIBLE : View.INVISIBLE); mMessage.setVisibility(state == ClusterViewModel.NavigationActivityState.NOT_SELECTED ? View.VISIBLE : View.INVISIBLE); }); return root; } private VirtualDisplay createVirtualDisplay(Surface surface, int width, int height) { Log.i(TAG, "createVirtualDisplay, surface: " + surface + ", width: " + width + "x" + height); return mDisplayManager.createVirtualDisplay("Cluster-App-VD", width, height, 160, surface, DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY); } @Override public void onDestroy() { super.onDestroy(); Log.i(TAG, "onDestroy"); } private int getVirtualDisplayId() { return (mVirtualDisplay != null && mVirtualDisplay.getDisplay() != null) ? mVirtualDisplay.getDisplay().getDisplayId() : Display.INVALID_DISPLAY; } }