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