1e54ac276796c6535558f8444d882adecd19ce2bdKeun-young Park/* 2e54ac276796c6535558f8444d882adecd19ce2bdKeun-young Park * Copyright (C) 2016 The Android Open Source Project 3e54ac276796c6535558f8444d882adecd19ce2bdKeun-young Park * 4e54ac276796c6535558f8444d882adecd19ce2bdKeun-young Park * Licensed under the Apache License, Version 2.0 (the "License"); 5e54ac276796c6535558f8444d882adecd19ce2bdKeun-young Park * you may not use this file except in compliance with the License. 6e54ac276796c6535558f8444d882adecd19ce2bdKeun-young Park * You may obtain a copy of the License at 7e54ac276796c6535558f8444d882adecd19ce2bdKeun-young Park * 8e54ac276796c6535558f8444d882adecd19ce2bdKeun-young Park * http://www.apache.org/licenses/LICENSE-2.0 9e54ac276796c6535558f8444d882adecd19ce2bdKeun-young Park * 10e54ac276796c6535558f8444d882adecd19ce2bdKeun-young Park * Unless required by applicable law or agreed to in writing, software 11e54ac276796c6535558f8444d882adecd19ce2bdKeun-young Park * distributed under the License is distributed on an "AS IS" BASIS, 12e54ac276796c6535558f8444d882adecd19ce2bdKeun-young Park * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13e54ac276796c6535558f8444d882adecd19ce2bdKeun-young Park * See the License for the specific language governing permissions and 14e54ac276796c6535558f8444d882adecd19ce2bdKeun-young Park * limitations under the License. 15e54ac276796c6535558f8444d882adecd19ce2bdKeun-young Park */ 16e54ac276796c6535558f8444d882adecd19ce2bdKeun-young Park 17e54ac276796c6535558f8444d882adecd19ce2bdKeun-young Parkpackage android.support.car.hardware; 18e54ac276796c6535558f8444d882adecd19ce2bdKeun-young Park 197bd39e929390e754c96e2792efe0deac7b3841e6Anthony Chenimport static androidx.annotation.RestrictTo.Scope.GROUP_ID; 207bd39e929390e754c96e2792efe0deac7b3841e6Anthony Chen 21f6e2de20ae9b13b7d4a6f5bed0f28ea2c8069742Vitalii Tomkivimport android.content.Context; 22e54ac276796c6535558f8444d882adecd19ce2bdKeun-young Parkimport android.support.car.CarNotConnectedException; 237bd39e929390e754c96e2792efe0deac7b3841e6Anthony Chen 247bd39e929390e754c96e2792efe0deac7b3841e6Anthony Chenimport androidx.annotation.RestrictTo; 257bd39e929390e754c96e2792efe0deac7b3841e6Anthony Chen 26f6e2de20ae9b13b7d4a6f5bed0f28ea2c8069742Vitalii Tomkivimport java.util.Collection; 27f6e2de20ae9b13b7d4a6f5bed0f28ea2c8069742Vitalii Tomkivimport java.util.HashSet; 28e54ac276796c6535558f8444d882adecd19ce2bdKeun-young Parkimport java.util.LinkedList; 29f6e2de20ae9b13b7d4a6f5bed0f28ea2c8069742Vitalii Tomkivimport java.util.Set; 30f6e2de20ae9b13b7d4a6f5bed0f28ea2c8069742Vitalii Tomkiv 31e54ac276796c6535558f8444d882adecd19ce2bdKeun-young Park/** 32e54ac276796c6535558f8444d882adecd19ce2bdKeun-young Park * @hide 33e54ac276796c6535558f8444d882adecd19ce2bdKeun-young Park */ 34141349407981da8a88f61c0b906240a1c3603ef7Keun-young Park@RestrictTo(GROUP_ID) 35e54ac276796c6535558f8444d882adecd19ce2bdKeun-young Parkpublic class CarSensorManagerEmbedded extends CarSensorManager { 36f6e2de20ae9b13b7d4a6f5bed0f28ea2c8069742Vitalii Tomkiv private static final String TAG = "CarSensorsProxy"; 37e54ac276796c6535558f8444d882adecd19ce2bdKeun-young Park 38e54ac276796c6535558f8444d882adecd19ce2bdKeun-young Park private final android.car.hardware.CarSensorManager mManager; 39f6e2de20ae9b13b7d4a6f5bed0f28ea2c8069742Vitalii Tomkiv private final CarSensorsProxy mCarSensorsProxy; 40d72b53500006e84b0c69e650878267c693c164a3Jason Tholstrup private final LinkedList<OnSensorChangedListenerProxy> mListeners = new LinkedList<>(); 41e54ac276796c6535558f8444d882adecd19ce2bdKeun-young Park 42f6e2de20ae9b13b7d4a6f5bed0f28ea2c8069742Vitalii Tomkiv public CarSensorManagerEmbedded(Object manager, Context context) { 43e54ac276796c6535558f8444d882adecd19ce2bdKeun-young Park mManager = (android.car.hardware.CarSensorManager) manager; 44d72b53500006e84b0c69e650878267c693c164a3Jason Tholstrup mCarSensorsProxy = new CarSensorsProxy(this, context); 45e54ac276796c6535558f8444d882adecd19ce2bdKeun-young Park } 46e54ac276796c6535558f8444d882adecd19ce2bdKeun-young Park 47e54ac276796c6535558f8444d882adecd19ce2bdKeun-young Park @Override 48e54ac276796c6535558f8444d882adecd19ce2bdKeun-young Park public int[] getSupportedSensors() throws CarNotConnectedException { 49e54ac276796c6535558f8444d882adecd19ce2bdKeun-young Park try { 50f6e2de20ae9b13b7d4a6f5bed0f28ea2c8069742Vitalii Tomkiv Set<Integer> sensorsSet = new HashSet<Integer>(); 51f6e2de20ae9b13b7d4a6f5bed0f28ea2c8069742Vitalii Tomkiv for (Integer sensor : mManager.getSupportedSensors()) { 52f6e2de20ae9b13b7d4a6f5bed0f28ea2c8069742Vitalii Tomkiv sensorsSet.add(sensor); 53f6e2de20ae9b13b7d4a6f5bed0f28ea2c8069742Vitalii Tomkiv } 54f6e2de20ae9b13b7d4a6f5bed0f28ea2c8069742Vitalii Tomkiv for (Integer proxySensor : mCarSensorsProxy.getSupportedSensors()) { 55f6e2de20ae9b13b7d4a6f5bed0f28ea2c8069742Vitalii Tomkiv sensorsSet.add(proxySensor); 56f6e2de20ae9b13b7d4a6f5bed0f28ea2c8069742Vitalii Tomkiv } 57f6e2de20ae9b13b7d4a6f5bed0f28ea2c8069742Vitalii Tomkiv return toIntArray(sensorsSet); 58e54ac276796c6535558f8444d882adecd19ce2bdKeun-young Park } catch (android.car.CarNotConnectedException e) { 59e54ac276796c6535558f8444d882adecd19ce2bdKeun-young Park throw new CarNotConnectedException(e); 60e54ac276796c6535558f8444d882adecd19ce2bdKeun-young Park } 61e54ac276796c6535558f8444d882adecd19ce2bdKeun-young Park } 62e54ac276796c6535558f8444d882adecd19ce2bdKeun-young Park 63f6e2de20ae9b13b7d4a6f5bed0f28ea2c8069742Vitalii Tomkiv private static int[] toIntArray(Collection<Integer> collection) { 64f6e2de20ae9b13b7d4a6f5bed0f28ea2c8069742Vitalii Tomkiv int len = collection.size(); 65f6e2de20ae9b13b7d4a6f5bed0f28ea2c8069742Vitalii Tomkiv int[] arr = new int[len]; 66f6e2de20ae9b13b7d4a6f5bed0f28ea2c8069742Vitalii Tomkiv int arrIndex = 0; 67f6e2de20ae9b13b7d4a6f5bed0f28ea2c8069742Vitalii Tomkiv for (Integer item : collection) { 68f6e2de20ae9b13b7d4a6f5bed0f28ea2c8069742Vitalii Tomkiv arr[arrIndex] = item; 69f6e2de20ae9b13b7d4a6f5bed0f28ea2c8069742Vitalii Tomkiv arrIndex++; 70f6e2de20ae9b13b7d4a6f5bed0f28ea2c8069742Vitalii Tomkiv } 71f6e2de20ae9b13b7d4a6f5bed0f28ea2c8069742Vitalii Tomkiv return arr; 72f6e2de20ae9b13b7d4a6f5bed0f28ea2c8069742Vitalii Tomkiv } 73f6e2de20ae9b13b7d4a6f5bed0f28ea2c8069742Vitalii Tomkiv 74e54ac276796c6535558f8444d882adecd19ce2bdKeun-young Park @Override 75e54ac276796c6535558f8444d882adecd19ce2bdKeun-young Park public boolean isSensorSupported(int sensorType) throws CarNotConnectedException { 76e54ac276796c6535558f8444d882adecd19ce2bdKeun-young Park try { 77f6e2de20ae9b13b7d4a6f5bed0f28ea2c8069742Vitalii Tomkiv return mManager.isSensorSupported(sensorType) 78f6e2de20ae9b13b7d4a6f5bed0f28ea2c8069742Vitalii Tomkiv || mCarSensorsProxy.isSensorSupported(sensorType); 79f6e2de20ae9b13b7d4a6f5bed0f28ea2c8069742Vitalii Tomkiv } catch (android.car.CarNotConnectedException e) { 80f6e2de20ae9b13b7d4a6f5bed0f28ea2c8069742Vitalii Tomkiv throw new CarNotConnectedException(e); 81f6e2de20ae9b13b7d4a6f5bed0f28ea2c8069742Vitalii Tomkiv } 82f6e2de20ae9b13b7d4a6f5bed0f28ea2c8069742Vitalii Tomkiv } 83f6e2de20ae9b13b7d4a6f5bed0f28ea2c8069742Vitalii Tomkiv 84f6e2de20ae9b13b7d4a6f5bed0f28ea2c8069742Vitalii Tomkiv private boolean isSensorProxied(int sensorType) throws CarNotConnectedException { 85f6e2de20ae9b13b7d4a6f5bed0f28ea2c8069742Vitalii Tomkiv try { 86f6e2de20ae9b13b7d4a6f5bed0f28ea2c8069742Vitalii Tomkiv return !mManager.isSensorSupported(sensorType) 87f6e2de20ae9b13b7d4a6f5bed0f28ea2c8069742Vitalii Tomkiv && mCarSensorsProxy.isSensorSupported(sensorType); 88e54ac276796c6535558f8444d882adecd19ce2bdKeun-young Park } catch (android.car.CarNotConnectedException e) { 89e54ac276796c6535558f8444d882adecd19ce2bdKeun-young Park throw new CarNotConnectedException(e); 90e54ac276796c6535558f8444d882adecd19ce2bdKeun-young Park } 91e54ac276796c6535558f8444d882adecd19ce2bdKeun-young Park } 92e54ac276796c6535558f8444d882adecd19ce2bdKeun-young Park 93e54ac276796c6535558f8444d882adecd19ce2bdKeun-young Park @Override 94d72b53500006e84b0c69e650878267c693c164a3Jason Tholstrup public boolean addListener(OnSensorChangedListener listener, int sensorType, 95e54ac276796c6535558f8444d882adecd19ce2bdKeun-young Park int rate) throws CarNotConnectedException, IllegalArgumentException { 96f6e2de20ae9b13b7d4a6f5bed0f28ea2c8069742Vitalii Tomkiv if (isSensorProxied(sensorType)) { 97f6e2de20ae9b13b7d4a6f5bed0f28ea2c8069742Vitalii Tomkiv return mCarSensorsProxy.registerSensorListener(listener, sensorType, rate); 98f6e2de20ae9b13b7d4a6f5bed0f28ea2c8069742Vitalii Tomkiv } 99d72b53500006e84b0c69e650878267c693c164a3Jason Tholstrup OnSensorChangedListenerProxy proxy = null; 100e54ac276796c6535558f8444d882adecd19ce2bdKeun-young Park synchronized (this) { 101e54ac276796c6535558f8444d882adecd19ce2bdKeun-young Park proxy = findListenerLocked(listener); 102e54ac276796c6535558f8444d882adecd19ce2bdKeun-young Park if (proxy == null) { 103d72b53500006e84b0c69e650878267c693c164a3Jason Tholstrup proxy = new OnSensorChangedListenerProxy(listener, sensorType, this); 104f6e2de20ae9b13b7d4a6f5bed0f28ea2c8069742Vitalii Tomkiv mListeners.add(proxy); 105e54ac276796c6535558f8444d882adecd19ce2bdKeun-young Park } else { 106f6e2de20ae9b13b7d4a6f5bed0f28ea2c8069742Vitalii Tomkiv proxy.sensors.add(sensorType); 107e54ac276796c6535558f8444d882adecd19ce2bdKeun-young Park } 108e54ac276796c6535558f8444d882adecd19ce2bdKeun-young Park } 109e54ac276796c6535558f8444d882adecd19ce2bdKeun-young Park try { 110e54ac276796c6535558f8444d882adecd19ce2bdKeun-young Park return mManager.registerListener(proxy, sensorType, rate); 111e54ac276796c6535558f8444d882adecd19ce2bdKeun-young Park } catch (android.car.CarNotConnectedException e) { 112e54ac276796c6535558f8444d882adecd19ce2bdKeun-young Park throw new CarNotConnectedException(e); 113e54ac276796c6535558f8444d882adecd19ce2bdKeun-young Park } 114e54ac276796c6535558f8444d882adecd19ce2bdKeun-young Park } 115e54ac276796c6535558f8444d882adecd19ce2bdKeun-young Park 116e54ac276796c6535558f8444d882adecd19ce2bdKeun-young Park @Override 117150d8de43e71a624106e90bcc04067414c42ef18Keun-young Park public void removeListener(OnSensorChangedListener listener) { 118f6e2de20ae9b13b7d4a6f5bed0f28ea2c8069742Vitalii Tomkiv mCarSensorsProxy.unregisterSensorListener(listener); 119d72b53500006e84b0c69e650878267c693c164a3Jason Tholstrup OnSensorChangedListenerProxy proxy = null; 120e54ac276796c6535558f8444d882adecd19ce2bdKeun-young Park synchronized (this) { 121e54ac276796c6535558f8444d882adecd19ce2bdKeun-young Park proxy = findListenerLocked(listener); 122e54ac276796c6535558f8444d882adecd19ce2bdKeun-young Park if (proxy == null) { 123e54ac276796c6535558f8444d882adecd19ce2bdKeun-young Park return; 124e54ac276796c6535558f8444d882adecd19ce2bdKeun-young Park } 125e54ac276796c6535558f8444d882adecd19ce2bdKeun-young Park mListeners.remove(proxy); 126e54ac276796c6535558f8444d882adecd19ce2bdKeun-young Park } 127150d8de43e71a624106e90bcc04067414c42ef18Keun-young Park mManager.unregisterListener(proxy); 128e54ac276796c6535558f8444d882adecd19ce2bdKeun-young Park } 129e54ac276796c6535558f8444d882adecd19ce2bdKeun-young Park 130e54ac276796c6535558f8444d882adecd19ce2bdKeun-young Park @Override 131150d8de43e71a624106e90bcc04067414c42ef18Keun-young Park public void removeListener(OnSensorChangedListener listener, int sensorType) { 132f6e2de20ae9b13b7d4a6f5bed0f28ea2c8069742Vitalii Tomkiv mCarSensorsProxy.unregisterSensorListener(listener, sensorType); 133d72b53500006e84b0c69e650878267c693c164a3Jason Tholstrup OnSensorChangedListenerProxy proxy = null; 134e54ac276796c6535558f8444d882adecd19ce2bdKeun-young Park synchronized (this) { 135e54ac276796c6535558f8444d882adecd19ce2bdKeun-young Park proxy = findListenerLocked(listener); 136e54ac276796c6535558f8444d882adecd19ce2bdKeun-young Park if (proxy == null) { 137e54ac276796c6535558f8444d882adecd19ce2bdKeun-young Park return; 138e54ac276796c6535558f8444d882adecd19ce2bdKeun-young Park } 139f6e2de20ae9b13b7d4a6f5bed0f28ea2c8069742Vitalii Tomkiv proxy.sensors.remove(sensorType); 140f6e2de20ae9b13b7d4a6f5bed0f28ea2c8069742Vitalii Tomkiv if (proxy.sensors.isEmpty()) { 141e54ac276796c6535558f8444d882adecd19ce2bdKeun-young Park mListeners.remove(proxy); 142e54ac276796c6535558f8444d882adecd19ce2bdKeun-young Park } 143e54ac276796c6535558f8444d882adecd19ce2bdKeun-young Park } 144150d8de43e71a624106e90bcc04067414c42ef18Keun-young Park mManager.unregisterListener(proxy, sensorType); 145e54ac276796c6535558f8444d882adecd19ce2bdKeun-young Park } 146e54ac276796c6535558f8444d882adecd19ce2bdKeun-young Park 147e54ac276796c6535558f8444d882adecd19ce2bdKeun-young Park @Override 148e54ac276796c6535558f8444d882adecd19ce2bdKeun-young Park public CarSensorEvent getLatestSensorEvent(int type) throws CarNotConnectedException { 149f6e2de20ae9b13b7d4a6f5bed0f28ea2c8069742Vitalii Tomkiv if (isSensorProxied(type)) { 150f6e2de20ae9b13b7d4a6f5bed0f28ea2c8069742Vitalii Tomkiv return mCarSensorsProxy.getLatestSensorEvent(type); 151f6e2de20ae9b13b7d4a6f5bed0f28ea2c8069742Vitalii Tomkiv } 152e54ac276796c6535558f8444d882adecd19ce2bdKeun-young Park try { 153e54ac276796c6535558f8444d882adecd19ce2bdKeun-young Park return convert(mManager.getLatestSensorEvent(type)); 154e54ac276796c6535558f8444d882adecd19ce2bdKeun-young Park } catch (android.car.CarNotConnectedException e) { 155e54ac276796c6535558f8444d882adecd19ce2bdKeun-young Park throw new CarNotConnectedException(e); 156e54ac276796c6535558f8444d882adecd19ce2bdKeun-young Park } 157e54ac276796c6535558f8444d882adecd19ce2bdKeun-young Park } 158e54ac276796c6535558f8444d882adecd19ce2bdKeun-young Park 159e54ac276796c6535558f8444d882adecd19ce2bdKeun-young Park @Override 160a2819901c1b4f56e4950dd5cb6bf6c9e8042cf4cSteve Paik public CarSensorConfig getSensorConfig(@SensorType int type) 161a2819901c1b4f56e4950dd5cb6bf6c9e8042cf4cSteve Paik throws CarNotConnectedException { 162a2819901c1b4f56e4950dd5cb6bf6c9e8042cf4cSteve Paik try { 163a2819901c1b4f56e4950dd5cb6bf6c9e8042cf4cSteve Paik return convert(mManager.getSensorConfig(type)); 164a2819901c1b4f56e4950dd5cb6bf6c9e8042cf4cSteve Paik } catch (android.car.CarNotConnectedException e) { 165a2819901c1b4f56e4950dd5cb6bf6c9e8042cf4cSteve Paik throw new CarNotConnectedException(e); 166a2819901c1b4f56e4950dd5cb6bf6c9e8042cf4cSteve Paik } 167a2819901c1b4f56e4950dd5cb6bf6c9e8042cf4cSteve Paik } 168a2819901c1b4f56e4950dd5cb6bf6c9e8042cf4cSteve Paik 169a2819901c1b4f56e4950dd5cb6bf6c9e8042cf4cSteve Paik @Override 170e54ac276796c6535558f8444d882adecd19ce2bdKeun-young Park public void onCarDisconnected() { 171e54ac276796c6535558f8444d882adecd19ce2bdKeun-young Park //nothing to do 172e54ac276796c6535558f8444d882adecd19ce2bdKeun-young Park } 173e54ac276796c6535558f8444d882adecd19ce2bdKeun-young Park 174d72b53500006e84b0c69e650878267c693c164a3Jason Tholstrup private OnSensorChangedListenerProxy findListenerLocked(OnSensorChangedListener listener) { 175d72b53500006e84b0c69e650878267c693c164a3Jason Tholstrup for (OnSensorChangedListenerProxy proxy : mListeners) { 176e54ac276796c6535558f8444d882adecd19ce2bdKeun-young Park if (proxy.listener == listener) { 177e54ac276796c6535558f8444d882adecd19ce2bdKeun-young Park return proxy; 178e54ac276796c6535558f8444d882adecd19ce2bdKeun-young Park } 179e54ac276796c6535558f8444d882adecd19ce2bdKeun-young Park } 180e54ac276796c6535558f8444d882adecd19ce2bdKeun-young Park return null; 181e54ac276796c6535558f8444d882adecd19ce2bdKeun-young Park } 182e54ac276796c6535558f8444d882adecd19ce2bdKeun-young Park 183e54ac276796c6535558f8444d882adecd19ce2bdKeun-young Park private static CarSensorEvent convert(android.car.hardware.CarSensorEvent event) { 184e54ac276796c6535558f8444d882adecd19ce2bdKeun-young Park if (event == null) { 185e54ac276796c6535558f8444d882adecd19ce2bdKeun-young Park return null; 186e54ac276796c6535558f8444d882adecd19ce2bdKeun-young Park } 187d72b53500006e84b0c69e650878267c693c164a3Jason Tholstrup return new CarSensorEvent(event.sensorType, event.timestamp, event.floatValues, 188289ab99688d226518e47a7e47c9ffc20f221f0a6Steve Paik event.intValues, event.longValues); 189e54ac276796c6535558f8444d882adecd19ce2bdKeun-young Park } 190e54ac276796c6535558f8444d882adecd19ce2bdKeun-young Park 191a2819901c1b4f56e4950dd5cb6bf6c9e8042cf4cSteve Paik private static CarSensorConfig convert(android.car.hardware.CarSensorConfig cfg) { 192a2819901c1b4f56e4950dd5cb6bf6c9e8042cf4cSteve Paik if (cfg == null) { 193a2819901c1b4f56e4950dd5cb6bf6c9e8042cf4cSteve Paik return null; 194a2819901c1b4f56e4950dd5cb6bf6c9e8042cf4cSteve Paik } 195a2819901c1b4f56e4950dd5cb6bf6c9e8042cf4cSteve Paik return new CarSensorConfig(cfg.getType(), cfg.getBundle()); 196a2819901c1b4f56e4950dd5cb6bf6c9e8042cf4cSteve Paik } 197a2819901c1b4f56e4950dd5cb6bf6c9e8042cf4cSteve Paik 198d72b53500006e84b0c69e650878267c693c164a3Jason Tholstrup private static class OnSensorChangedListenerProxy 199d72b53500006e84b0c69e650878267c693c164a3Jason Tholstrup implements android.car.hardware.CarSensorManager.OnSensorChangedListener { 200e54ac276796c6535558f8444d882adecd19ce2bdKeun-young Park 201d72b53500006e84b0c69e650878267c693c164a3Jason Tholstrup public final OnSensorChangedListener listener; 202d72b53500006e84b0c69e650878267c693c164a3Jason Tholstrup public final Set<Integer> sensors = new HashSet<>(); 203d72b53500006e84b0c69e650878267c693c164a3Jason Tholstrup public final CarSensorManager manager; 204e54ac276796c6535558f8444d882adecd19ce2bdKeun-young Park 205d72b53500006e84b0c69e650878267c693c164a3Jason Tholstrup OnSensorChangedListenerProxy(OnSensorChangedListener listener, int sensor, 206d72b53500006e84b0c69e650878267c693c164a3Jason Tholstrup CarSensorManager manager) { 207e54ac276796c6535558f8444d882adecd19ce2bdKeun-young Park this.listener = listener; 208f6e2de20ae9b13b7d4a6f5bed0f28ea2c8069742Vitalii Tomkiv this.sensors.add(sensor); 209d72b53500006e84b0c69e650878267c693c164a3Jason Tholstrup this.manager = manager; 210e54ac276796c6535558f8444d882adecd19ce2bdKeun-young Park } 211e54ac276796c6535558f8444d882adecd19ce2bdKeun-young Park 212e54ac276796c6535558f8444d882adecd19ce2bdKeun-young Park @Override 213e54ac276796c6535558f8444d882adecd19ce2bdKeun-young Park public void onSensorChanged(android.car.hardware.CarSensorEvent event) { 214e54ac276796c6535558f8444d882adecd19ce2bdKeun-young Park CarSensorEvent newEvent = convert(event); 215d72b53500006e84b0c69e650878267c693c164a3Jason Tholstrup listener.onSensorChanged(manager, newEvent); 216e54ac276796c6535558f8444d882adecd19ce2bdKeun-young Park } 217e54ac276796c6535558f8444d882adecd19ce2bdKeun-young Park } 218e54ac276796c6535558f8444d882adecd19ce2bdKeun-young Park} 219