1 /*
2  * Copyright (C) 2017 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package android.car.cluster;
17 
18 import android.content.Context;
19 import android.graphics.Rect;
20 import android.hardware.display.DisplayManager;
21 import android.hardware.display.DisplayManager.DisplayListener;
22 import android.hardware.display.VirtualDisplay;
23 import android.os.Bundle;
24 import android.os.Handler;
25 import android.util.Log;
26 import android.view.Display;
27 import android.view.LayoutInflater;
28 import android.view.Surface;
29 import android.view.SurfaceHolder;
30 import android.view.SurfaceHolder.Callback;
31 import android.view.SurfaceView;
32 import android.view.View;
33 import android.view.ViewGroup;
34 import android.widget.ProgressBar;
35 import android.widget.TextView;
36 
37 import androidx.fragment.app.Fragment;
38 import androidx.lifecycle.ViewModelProvider;
39 import androidx.lifecycle.ViewModelProviders;
40 
41 public class NavigationFragment extends Fragment {
42     private static final String TAG = "Cluster.NavFragment";
43 
44     private SurfaceView mSurfaceView;
45     private DisplayManager mDisplayManager;
46     private Rect mUnobscuredBounds;
47     private MainClusterActivity mMainClusterActivity;
48     private ClusterViewModel mViewModel;
49     private ProgressBar mProgressBar;
50     private TextView mMessage;
51 
52 
53     // Static because we want to keep alive this virtual display when navigating through
54     // ViewPager (this fragment gets dynamically destroyed and created)
55     private static VirtualDisplay mVirtualDisplay;
56     private static int mRegisteredNavDisplayId = Display.INVALID_DISPLAY;
57     private boolean mNavigationDisplayUpdatePending = false;
58 
NavigationFragment()59     public NavigationFragment() {
60         // Required empty public constructor
61     }
62 
63 
64     private final DisplayListener mDisplayListener = new DisplayListener() {
65         @Override
66         public void onDisplayAdded(int displayId) {
67             int navDisplayId = getVirtualDisplayId();
68             Log.i(TAG, "onDisplayAdded, displayId: " + displayId
69                     + ", navigation display id: " + navDisplayId);
70 
71             if (navDisplayId == displayId) {
72                 mRegisteredNavDisplayId = displayId;
73                 updateNavigationDisplay();
74             }
75         }
76 
77         @Override
78         public void onDisplayRemoved(int displayId) {
79             if (mRegisteredNavDisplayId == displayId) {
80                 mRegisteredNavDisplayId = Display.INVALID_DISPLAY;
81                 updateNavigationDisplay();
82             }
83         }
84 
85         @Override
86         public void onDisplayChanged(int displayId) {}
87     };
88 
updateNavigationDisplay()89     private void updateNavigationDisplay() {
90         if (mMainClusterActivity == null) {
91             // Not attached to the activity yet. Let's wait.
92             mNavigationDisplayUpdatePending = true;
93             return;
94         }
95 
96         mNavigationDisplayUpdatePending = false;
97         mMainClusterActivity.updateNavDisplay(new MainClusterActivity.VirtualDisplay(
98                 mRegisteredNavDisplayId, mUnobscuredBounds));
99     }
100 
101     @Override
onAttach(Context context)102     public void onAttach(Context context) {
103         super.onAttach(context);
104         mMainClusterActivity = (MainClusterActivity) context;
105         if (mNavigationDisplayUpdatePending) {
106             updateNavigationDisplay();
107         }
108     }
109 
110     @Override
onDetach()111     public void onDetach() {
112         mMainClusterActivity = null;
113         super.onDetach();
114     }
115 
116     @Override
onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)117     public View onCreateView(LayoutInflater inflater, ViewGroup container,
118                              Bundle savedInstanceState) {
119         Log.i(TAG, "onCreateView");
120         ViewModelProvider provider = ViewModelProviders.of(requireActivity());
121         mViewModel = provider.get(ClusterViewModel.class);
122 
123         mDisplayManager = getActivity().getSystemService(DisplayManager.class);
124         mDisplayManager.registerDisplayListener(mDisplayListener, new Handler());
125 
126         // Inflate the layout for this fragment
127         View root = inflater.inflate(R.layout.fragment_navigation, container, false);
128 
129         mSurfaceView = root.findViewById(R.id.nav_surface);
130         mSurfaceView.getHolder().addCallback(new Callback() {
131             @Override
132             public void surfaceCreated(SurfaceHolder holder) {
133                 Log.i(TAG, "surfaceCreated, holder: " + holder);
134             }
135 
136             @Override
137             public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
138                 Log.i(TAG, "surfaceChanged, holder: " + holder + ", size:" + width + "x" + height
139                         + ", format:" + format);
140 
141                 // Create dummy unobscured area to report to navigation activity.
142                 int obscuredWidth = (int) getResources()
143                         .getDimension(R.dimen.speedometer_overlap_width);
144                 int obscuredHeight = (int) getResources()
145                         .getDimension(R.dimen.navigation_gradient_height);
146                 mUnobscuredBounds = new Rect(
147                         obscuredWidth,          /* left: size of gauge */
148                         obscuredHeight,         /* top: gradient */
149                         width - obscuredWidth,  /* right: size of the display - size of gauge */
150                         height - obscuredHeight /* bottom: size of display - gradient */
151                 );
152 
153                 if (mVirtualDisplay == null) {
154                     mVirtualDisplay = createVirtualDisplay(holder.getSurface(), width, height);
155                 } else {
156                     mVirtualDisplay.setSurface(holder.getSurface());
157                 }
158             }
159 
160             @Override
161             public void surfaceDestroyed(SurfaceHolder holder) {
162                 Log.i(TAG, "surfaceDestroyed, holder: " + holder + ", detaching surface from"
163                         + " display, surface: " + holder.getSurface());
164                 // detaching surface is similar to turning off the display
165                 mVirtualDisplay.setSurface(null);
166             }
167         });
168         mProgressBar = root.findViewById(R.id.progress_bar);
169         mMessage = root.findViewById(R.id.message);
170 
171         mViewModel.getNavigationActivityState().observe(this, state -> {
172             if (Log.isLoggable(TAG, Log.DEBUG)) {
173                 Log.d(TAG, "State: " + state);
174             }
175             mProgressBar.setVisibility(state == ClusterViewModel.NavigationActivityState.LOADING
176                     ? View.VISIBLE : View.INVISIBLE);
177             mMessage.setVisibility(state == ClusterViewModel.NavigationActivityState.NOT_SELECTED
178                     ? View.VISIBLE : View.INVISIBLE);
179         });
180 
181         return root;
182     }
183 
createVirtualDisplay(Surface surface, int width, int height)184     private VirtualDisplay createVirtualDisplay(Surface surface, int width, int height) {
185         Log.i(TAG, "createVirtualDisplay, surface: " + surface + ", width: " + width
186                 + "x" + height);
187         return mDisplayManager.createVirtualDisplay("Cluster-App-VD", width, height, 160, surface,
188                 DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY);
189     }
190 
191     @Override
onDestroy()192     public void onDestroy() {
193         super.onDestroy();
194         Log.i(TAG, "onDestroy");
195     }
196 
getVirtualDisplayId()197     private int getVirtualDisplayId() {
198         return (mVirtualDisplay != null && mVirtualDisplay.getDisplay() != null)
199                 ? mVirtualDisplay.getDisplay().getDisplayId() : Display.INVALID_DISPLAY;
200     }
201 }
202