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.Bundle;
22import android.os.Handler;
23import android.os.Looper;
24import android.os.Message;
25
26import java.lang.ref.WeakReference;
27import java.util.concurrent.CountDownLatch;
28
29/**
30 * A wrapper over {@link NavigationRenderer} that runs all its methods in the context of provided
31 * looper. It is guaranteed that all calls will be invoked in order they were called.
32 */
33// TODO(deanh): Does this class even need to exist?
34/* package */ class ThreadSafeNavigationRenderer extends NavigationRenderer {
35
36    private final Handler mHandler;
37    private final NavigationRenderer mRenderer;
38
39    private final static int MSG_EVENT = 1;
40
41    /** Creates thread-safe {@link NavigationRenderer}. Returns null if renderer == null */
42    @Nullable
43    static NavigationRenderer createFor(Looper looper, NavigationRenderer renderer) {
44        return renderer == null ? null : new ThreadSafeNavigationRenderer(looper, renderer);
45    }
46
47    private ThreadSafeNavigationRenderer(Looper looper, NavigationRenderer renderer) {
48        mRenderer = renderer;
49        mHandler = new NavigationRendererHandler(looper, renderer);
50    }
51
52    @Override
53    public CarNavigationInstrumentCluster getNavigationProperties() {
54        if (mHandler.getLooper() == Looper.myLooper()) {
55            return mRenderer.getNavigationProperties();
56        } else {
57            return runAndWaitResult(mHandler,
58                    new RunnableWithResult<CarNavigationInstrumentCluster>() {
59                        @Override
60                        protected CarNavigationInstrumentCluster createResult() {
61                            return mRenderer.getNavigationProperties();
62                        }
63                    });
64        }
65    }
66
67    @Override
68    public void onEvent(int eventType, Bundle bundle) {
69        mHandler.sendMessage(mHandler.obtainMessage(MSG_EVENT, eventType, 0, bundle));
70    }
71
72    private static class NavigationRendererHandler extends RendererHandler<NavigationRenderer> {
73
74        NavigationRendererHandler(Looper looper, NavigationRenderer renderer) {
75            super(looper, renderer);
76        }
77
78        @Override
79        public void handleMessage(Message msg, NavigationRenderer renderer) {
80            switch (msg.what) {
81                case MSG_EVENT:
82                    Bundle bundle = (Bundle) msg.obj;
83                    renderer.onEvent(msg.arg1, bundle);
84                    break;
85                default:
86                    throw new IllegalArgumentException("Msg: " + msg.what);
87            }
88        }
89    }
90
91    private static <E> E runAndWaitResult(Handler handler, final RunnableWithResult<E> runnable) {
92        final CountDownLatch latch = new CountDownLatch(1);
93
94        Runnable wrappedRunnable = new Runnable() {
95            @Override
96            public void run() {
97                runnable.run();
98                latch.countDown();
99            }
100        };
101
102        handler.post(wrappedRunnable);
103
104        try {
105            latch.await();
106        } catch (InterruptedException e) {
107            throw new RuntimeException(e);
108        }
109        return runnable.getResult();
110    }
111
112    private static abstract class RunnableWithResult<T> implements Runnable {
113        private volatile T result;
114
115        protected abstract T createResult();
116
117        @Override
118        public void run() {
119            result = createResult();
120        }
121
122        public T getResult() {
123            return result;
124        }
125    }
126
127    private static abstract class RendererHandler<T> extends Handler {
128
129        private final WeakReference<T> mRendererRef;
130
131        RendererHandler(Looper looper, T renderer) {
132            super(looper);
133            mRendererRef = new WeakReference<>(renderer);
134        }
135
136        @Override
137        public void handleMessage(Message msg) {
138            T renderer = mRendererRef.get();
139            if (renderer != null) {
140                handleMessage(msg, renderer);
141            }
142        }
143
144        public abstract void handleMessage(Message msg, T renderer);
145    }
146}
147