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 */
16package android.car.cluster.renderer;
17
18import android.annotation.Nullable;
19import android.car.navigation.CarNavigationInstrumentCluster;
20import android.graphics.Bitmap;
21import android.os.Handler;
22import android.os.Looper;
23import android.os.Message;
24
25import java.lang.ref.WeakReference;
26import java.util.concurrent.CountDownLatch;
27
28/**
29 * A wrapper over {@link NavigationRenderer} that runs all its methods in the context of provided
30 * looper. It is guaranteed that all calls will be invoked in order they were called.
31 */
32/* package */ class ThreadSafeNavigationRenderer extends NavigationRenderer {
33
34    private final Handler mHandler;
35    private final NavigationRenderer mRenderer;
36
37    private final static int MSG_NAV_START = 1;
38    private final static int MSG_NAV_STOP = 2;
39    private final static int MSG_NAV_NEXT_TURN = 3;
40    private final static int MSG_NAV_NEXT_TURN_DISTANCE = 4;
41
42    /** Creates thread-safe {@link NavigationRenderer}. Returns null if renderer == null */
43    @Nullable
44    static NavigationRenderer createFor(Looper looper, NavigationRenderer renderer) {
45        return renderer == null ? null : new ThreadSafeNavigationRenderer(looper, renderer);
46    }
47
48    private ThreadSafeNavigationRenderer(Looper looper, NavigationRenderer renderer) {
49        mRenderer = renderer;
50        mHandler = new NavigationRendererHandler(looper, renderer);
51    }
52
53    @Override
54    public CarNavigationInstrumentCluster getNavigationProperties() {
55        if (mHandler.getLooper() == Looper.myLooper()) {
56            return mRenderer.getNavigationProperties();
57        } else {
58            return runAndWaitResult(mHandler,
59                    new RunnableWithResult<CarNavigationInstrumentCluster>() {
60                        @Override
61                        protected CarNavigationInstrumentCluster createResult() {
62                            return mRenderer.getNavigationProperties();
63                        }
64                    });
65        }
66    }
67
68    @Override
69    public void onStartNavigation() {
70        mHandler.sendMessage(mHandler.obtainMessage(MSG_NAV_START));
71    }
72
73    @Override
74    public void onStopNavigation() {
75        mHandler.sendMessage(mHandler.obtainMessage(MSG_NAV_STOP));
76    }
77
78    @Override
79    public void onNextTurnChanged(int event, CharSequence eventName, int turnAngle, int turnNumber,
80            Bitmap image, int turnSide) {
81        mHandler.sendMessage(mHandler.obtainMessage(MSG_NAV_NEXT_TURN,
82                new NextTurn(event, eventName, turnAngle, turnNumber, image, turnSide)));
83    }
84
85    @Override
86    public void onNextTurnDistanceChanged(int distanceMeters, int timeSeconds,
87            int displayDistanceMillis, int displayDistanceUnit) {
88        ManeuverDistance distance = new ManeuverDistance(distanceMeters, timeSeconds,
89                displayDistanceMillis, displayDistanceUnit);
90        mHandler.sendMessage(mHandler.obtainMessage(MSG_NAV_NEXT_TURN_DISTANCE, distance));
91    }
92
93    private static class NavigationRendererHandler extends RendererHandler<NavigationRenderer> {
94
95        NavigationRendererHandler(Looper looper, NavigationRenderer renderer) {
96            super(looper, renderer);
97        }
98
99        @Override
100        public void handleMessage(Message msg, NavigationRenderer renderer) {
101            switch (msg.what) {
102                case MSG_NAV_START:
103                    renderer.onStartNavigation();
104                    break;
105                case MSG_NAV_STOP:
106                    renderer.onStopNavigation();
107                    break;
108                case MSG_NAV_NEXT_TURN:
109                    NextTurn nt = (NextTurn) msg.obj;
110                    renderer.onNextTurnChanged(nt.event, nt.eventName, nt.turnAngle, nt.turnNumber,
111                            nt.bitmap, nt.turnSide);
112                    break;
113                case MSG_NAV_NEXT_TURN_DISTANCE:
114                    ManeuverDistance d = (ManeuverDistance) msg.obj;
115                    renderer.onNextTurnDistanceChanged(
116                            d.meters, d.seconds, d.displayDistanceMillis, d.displayDistanceUnit);
117                    break;
118                default:
119                    throw new IllegalArgumentException("Msg: " + msg.what);
120            }
121        }
122    }
123
124    private static <E> E runAndWaitResult(Handler handler, final RunnableWithResult<E> runnable) {
125        final CountDownLatch latch = new CountDownLatch(1);
126
127        Runnable wrappedRunnable = new Runnable() {
128            @Override
129            public void run() {
130                runnable.run();
131                latch.countDown();
132            }
133        };
134
135        handler.post(wrappedRunnable);
136
137        try {
138            latch.await();
139        } catch (InterruptedException e) {
140            throw new RuntimeException(e);
141        }
142        return runnable.getResult();
143    }
144
145    private static class NextTurn {
146        private final int event;
147        private final CharSequence eventName;
148        private final int turnAngle;
149        private final int turnNumber;
150        private final Bitmap bitmap;
151        private final int turnSide;
152
153        NextTurn(int event, CharSequence eventName, int turnAngle, int turnNumber, Bitmap bitmap,
154                int turnSide) {
155            this.event = event;
156            this.eventName = eventName;
157            this.turnAngle = turnAngle;
158            this.turnNumber = turnNumber;
159            this.bitmap = bitmap;
160            this.turnSide = turnSide;
161        }
162    }
163
164    private static abstract class RunnableWithResult<T> implements Runnable {
165        private volatile T result;
166
167        protected abstract T createResult();
168
169        @Override
170        public void run() {
171            result = createResult();
172        }
173
174        public T getResult() {
175            return result;
176        }
177    }
178
179    private static abstract class RendererHandler<T> extends Handler {
180
181        private final WeakReference<T> mRendererRef;
182
183        RendererHandler(Looper looper, T renderer) {
184            super(looper);
185            mRendererRef = new WeakReference<>(renderer);
186        }
187
188        @Override
189        public void handleMessage(Message msg) {
190            T renderer = mRendererRef.get();
191            if (renderer != null) {
192                handleMessage(msg, renderer);
193            }
194        }
195
196        public abstract void handleMessage(Message msg, T renderer);
197    }
198
199    private static class ManeuverDistance {
200        final int meters;
201        final int seconds;
202        final int displayDistanceMillis;
203        final int displayDistanceUnit;
204
205        ManeuverDistance(int meters, int seconds, int displayDistanceMillis,
206                int displayDistanceUnit) {
207            this.meters = meters;
208            this.seconds = seconds;
209            this.displayDistanceMillis = displayDistanceMillis;
210            this.displayDistanceUnit = displayDistanceUnit;
211        }
212    }
213}
214