1 /*
2  * Copyright (C) 2016 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 com.android.car.cluster;
17 
18 import static com.android.car.cluster.InstrumentClusterRendererLoader.createRenderer;
19 import static com.android.car.cluster.InstrumentClusterRendererLoader.createRendererPackageContext;
20 
21 import android.annotation.IntDef;
22 import android.annotation.Nullable;
23 import android.annotation.SystemApi;
24 import android.car.cluster.renderer.InstrumentClusterRenderer;
25 import android.car.cluster.renderer.NavigationRenderer;
26 import android.content.Context;
27 import android.hardware.display.DisplayManager;
28 import android.os.Handler;
29 import android.os.Looper;
30 import android.os.Message;
31 import android.util.Log;
32 import android.view.Display;
33 import android.view.LayoutInflater;
34 import android.view.View;
35 import android.view.ViewGroup;
36 
37 import com.android.car.CarLog;
38 import com.android.car.CarServiceBase;
39 import com.android.car.CarServiceUtils;
40 import com.android.car.R;
41 
42 import java.io.PrintWriter;
43 import java.lang.annotation.Retention;
44 import java.lang.annotation.RetentionPolicy;
45 import java.util.concurrent.CopyOnWriteArrayList;
46 
47 /**
48  * Service responsible for interaction with car's instrument cluster.
49  *
50  * @hide
51  */
52 @SystemApi
53 public class InstrumentClusterService implements CarServiceBase {
54 
55     private static final String TAG = CarLog.TAG_CLUSTER + "."
56             + InstrumentClusterService.class.getSimpleName();
57 
58     private static final int RENDERER_INIT_TIMEOUT_MS = 10 * 1000;
59     private final static int MSG_TIMEOUT = 1;
60 
61     private final Context mContext;
62     private final Object mHalSync = new Object();
63     private final CopyOnWriteArrayList<RendererInitializationListener>
64             mRendererInitializationListeners = new CopyOnWriteArrayList<>();
65 
66     private InstrumentClusterRenderer mRenderer;
67 
68     private final Handler mHandler;
69 
InstrumentClusterService(Context context)70     public InstrumentClusterService(Context context) {
71         mContext = context;
72         mHandler = new TimeoutHandler();
73     }
74 
75     public interface RendererInitializationListener {
onRendererInitSucceeded()76         void onRendererInitSucceeded();
77     }
78 
79     @Override
init()80     public void init() {
81         Log.d(TAG, "init");
82 
83         if (getInstrumentClusterType() == InstrumentClusterType.GRAPHICS) {
84             Display display = getInstrumentClusterDisplay(mContext);
85             boolean rendererFound = InstrumentClusterRendererLoader.isRendererAvailable(mContext);
86 
87             if (display != null && rendererFound) {
88                 initRendererOnMainThread(display);
89                 mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_TIMEOUT),
90                         RENDERER_INIT_TIMEOUT_MS);
91             } else {
92                 mClusterType = InstrumentClusterType.NONE;
93                 Log.w(TAG, "Failed to initialize InstrumentClusterRenderer"
94                         + ", renderer found: " + rendererFound
95                         + ", secondary display: " + (display != null), new RuntimeException());
96 
97                 return;
98             }
99         }
100     }
101 
initRendererOnMainThread(final Display display)102     private void initRendererOnMainThread(final Display display) {
103         CarServiceUtils.runOnMain(new Runnable() {
104             @Override
105             public void run() {
106                 Log.d(TAG, "initRendererOnMainThread");
107                 try {
108                     InstrumentClusterPresentation presentation =
109                             new InstrumentClusterPresentation(mContext, display);
110 
111                     ViewGroup rootView = (ViewGroup) LayoutInflater.from(mContext).inflate(
112                             R.layout.instrument_cluster, null);
113 
114                     presentation.setContentView(rootView);
115                     InstrumentClusterRenderer renderer = createRenderer(mContext);
116                     renderer.onCreate(createRendererPackageContext(mContext));
117                     View rendererView = renderer.onCreateView(null);
118                     renderer.initialize();
119                     rootView.addView(rendererView);
120                     presentation.show();
121                     renderer.onStart();
122                     initUiDone(renderer);
123                 } catch (Exception e) {
124                     Log.e(TAG, e.getMessage(), e);
125                     throw e;
126                 }
127             }
128         });
129     }
130 
initUiDone(final InstrumentClusterRenderer renderer)131     private void initUiDone(final InstrumentClusterRenderer renderer) {
132         Log.d(TAG, "initUiDone");
133         mHandler.removeMessages(MSG_TIMEOUT);
134 
135         // Call listeners in service thread.
136         runOnServiceThread(new Runnable() {
137             @Override
138             public void run() {
139                 mRenderer = renderer;
140 
141                 for (RendererInitializationListener listener : mRendererInitializationListeners) {
142                     listener.onRendererInitSucceeded();
143                 }
144             }
145         });
146     }
147 
148     @Override
release()149     public void release() {
150         Log.d(TAG, "release");
151     }
152 
153     @Override
dump(PrintWriter writer)154     public void dump(PrintWriter writer) {
155         // TODO
156     }
157 
158     @Retention(RetentionPolicy.SOURCE)
159     @IntDef({
160             InstrumentClusterType.NONE,
161             InstrumentClusterType.METADATA,
162             InstrumentClusterType.GRAPHICS
163     })
164     public @interface InstrumentClusterType {
165         /**
166          * For use privately in this class.
167          * @hide
168          */
169         int UNDEFINED = -1;
170 
171         /** Access to instrument cluster is not available */
172         int NONE = 0;
173 
174         /** Access to instrument cluster through vehicle HAL using meta-data. */
175         int METADATA = 1;
176 
177         /** Access instrument cluster as a secondary display. */
178         int GRAPHICS = 2;
179     }
180 
181     @InstrumentClusterType private int mClusterType = InstrumentClusterType.UNDEFINED;
182 
isInstrumentClusterAvailable()183     public boolean isInstrumentClusterAvailable() {
184         return mClusterType != InstrumentClusterType.NONE
185                 && mClusterType != InstrumentClusterType.UNDEFINED;
186     }
187 
getInstrumentClusterType()188     public int getInstrumentClusterType() {
189         if (mClusterType == InstrumentClusterType.UNDEFINED) {
190             synchronized (mHalSync) {
191                 // TODO: need to pull this information from the HAL
192                 mClusterType = getInstrumentClusterDisplay(mContext) != null
193                         ? InstrumentClusterType.GRAPHICS : InstrumentClusterType.NONE;
194             }
195         }
196         return mClusterType;
197     }
198 
199     @Nullable
getNavigationRenderer()200     public NavigationRenderer getNavigationRenderer() {
201         return mRenderer != null ? mRenderer.getNavigationRenderer() : null;
202     }
203 
registerListener(RendererInitializationListener listener)204     public void registerListener(RendererInitializationListener listener) {
205         mRendererInitializationListeners.add(listener);
206     }
207 
unregisterListener(RendererInitializationListener listener)208     public void unregisterListener(RendererInitializationListener listener) {
209         mRendererInitializationListeners.remove(listener);
210     }
211 
getInstrumentClusterDisplay(Context context)212     private static Display getInstrumentClusterDisplay(Context context) {
213         DisplayManager displayManager = context.getSystemService(DisplayManager.class);
214         Display[] displays = displayManager.getDisplays();
215 
216         Log.d(TAG, "There are currently " + displays.length + " displays connected.");
217         for (Display display : displays) {
218             Log.d(TAG, "  " + display);
219         }
220 
221         if (displays.length > 1) {
222             // TODO: assuming that secondary display is instrument cluster. Put this into settings?
223             return displays[1];
224         }
225         return null;
226     }
227 
runOnServiceThread(final Runnable runnable)228     private void runOnServiceThread(final Runnable runnable) {
229         if (Looper.myLooper() == mHandler.getLooper()) {
230             runnable.run();
231         } else {
232             mHandler.post(runnable);
233         }
234     }
235 
236     private static class TimeoutHandler extends Handler {
237         @Override
handleMessage(Message msg)238         public void handleMessage(Message msg) {
239             if (msg.what == MSG_TIMEOUT) {
240                 Log.e(TAG, "Renderer initialization timeout.", new RuntimeException());
241             } else {
242                 super.handleMessage(msg);
243             }
244         }
245     }
246 }
247