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