1c22dbb69194c8e8fe2a32326d1f37a738cad0904Wonsik Kim/*
2c22dbb69194c8e8fe2a32326d1f37a738cad0904Wonsik Kim * Copyright 2014 The Android Open Source Project
3c22dbb69194c8e8fe2a32326d1f37a738cad0904Wonsik Kim *
4c22dbb69194c8e8fe2a32326d1f37a738cad0904Wonsik Kim * Licensed under the Apache License, Version 2.0 (the "License");
5c22dbb69194c8e8fe2a32326d1f37a738cad0904Wonsik Kim * you may not use this file except in compliance with the License.
6c22dbb69194c8e8fe2a32326d1f37a738cad0904Wonsik Kim * You may obtain a copy of the License at
7c22dbb69194c8e8fe2a32326d1f37a738cad0904Wonsik Kim *
8c22dbb69194c8e8fe2a32326d1f37a738cad0904Wonsik Kim *      http://www.apache.org/licenses/LICENSE-2.0
9c22dbb69194c8e8fe2a32326d1f37a738cad0904Wonsik Kim *
10c22dbb69194c8e8fe2a32326d1f37a738cad0904Wonsik Kim * Unless required by applicable law or agreed to in writing, software
11c22dbb69194c8e8fe2a32326d1f37a738cad0904Wonsik Kim * distributed under the License is distributed on an "AS IS" BASIS,
12c22dbb69194c8e8fe2a32326d1f37a738cad0904Wonsik Kim * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13c22dbb69194c8e8fe2a32326d1f37a738cad0904Wonsik Kim * See the License for the specific language governing permissions and
14c22dbb69194c8e8fe2a32326d1f37a738cad0904Wonsik Kim * limitations under the License.
15c22dbb69194c8e8fe2a32326d1f37a738cad0904Wonsik Kim */
16c22dbb69194c8e8fe2a32326d1f37a738cad0904Wonsik Kim
17c22dbb69194c8e8fe2a32326d1f37a738cad0904Wonsik Kimpackage com.android.server.tv;
18c22dbb69194c8e8fe2a32326d1f37a738cad0904Wonsik Kim
1939f285d5a75f653d2d2309b47cfaa143da42a0caShubangimport android.hardware.tv.input.V1_0.Constants;
20d5cc4a281e7ce29d1e8687ff3394b57a3a549260Jae Seoimport android.media.tv.TvInputHardwareInfo;
21d5cc4a281e7ce29d1e8687ff3394b57a3a549260Jae Seoimport android.media.tv.TvStreamConfig;
22c22dbb69194c8e8fe2a32326d1f37a738cad0904Wonsik Kimimport android.os.Handler;
239e922ca97097cb1aa67ff53219d874ea2503a80dWonsik Kimimport android.os.Message;
2457b37f610d33989f1b23e1b8d9e61fb177456364Wonsik Kimimport android.os.MessageQueue;
259e922ca97097cb1aa67ff53219d874ea2503a80dWonsik Kimimport android.util.Slog;
2621aa3467cd14260418cc47334b656adf841a567cWonsik Kimimport android.util.SparseArray;
2721aa3467cd14260418cc47334b656adf841a567cWonsik Kimimport android.util.SparseIntArray;
285b1caaf7d8408bf0ce78d8d7a36f4649dda17797Jae Seoimport android.view.Surface;
299e922ca97097cb1aa67ff53219d874ea2503a80dWonsik Kim
309e922ca97097cb1aa67ff53219d874ea2503a80dWonsik Kimimport java.util.LinkedList;
319e922ca97097cb1aa67ff53219d874ea2503a80dWonsik Kimimport java.util.Queue;
32c22dbb69194c8e8fe2a32326d1f37a738cad0904Wonsik Kim
33c22dbb69194c8e8fe2a32326d1f37a738cad0904Wonsik Kim/**
34c22dbb69194c8e8fe2a32326d1f37a738cad0904Wonsik Kim * Provides access to the low-level TV input hardware abstraction layer.
35c22dbb69194c8e8fe2a32326d1f37a738cad0904Wonsik Kim */
369e922ca97097cb1aa67ff53219d874ea2503a80dWonsik Kimfinal class TvInputHal implements Handler.Callback {
37ee2ec05ed7c0d3cb9115f4ddd7c3613269c4a57bJae Seo    private final static boolean DEBUG = false;
389e922ca97097cb1aa67ff53219d874ea2503a80dWonsik Kim    private final static String TAG = TvInputHal.class.getSimpleName();
399e922ca97097cb1aa67ff53219d874ea2503a80dWonsik Kim
40c22dbb69194c8e8fe2a32326d1f37a738cad0904Wonsik Kim    public final static int SUCCESS = 0;
41c22dbb69194c8e8fe2a32326d1f37a738cad0904Wonsik Kim    public final static int ERROR_NO_INIT = -1;
42c22dbb69194c8e8fe2a32326d1f37a738cad0904Wonsik Kim    public final static int ERROR_STALE_CONFIG = -2;
43c22dbb69194c8e8fe2a32326d1f37a738cad0904Wonsik Kim    public final static int ERROR_UNKNOWN = -3;
44c22dbb69194c8e8fe2a32326d1f37a738cad0904Wonsik Kim
4539f285d5a75f653d2d2309b47cfaa143da42a0caShubang    public static final int EVENT_DEVICE_AVAILABLE = Constants.EVENT_DEVICE_AVAILABLE;
4639f285d5a75f653d2d2309b47cfaa143da42a0caShubang    public static final int EVENT_DEVICE_UNAVAILABLE = Constants.EVENT_DEVICE_UNAVAILABLE;
4739f285d5a75f653d2d2309b47cfaa143da42a0caShubang    public static final int EVENT_STREAM_CONFIGURATION_CHANGED =
4839f285d5a75f653d2d2309b47cfaa143da42a0caShubang            Constants.EVENT_STREAM_CONFIGURATIONS_CHANGED;
49c086a3df3b28996cd10ebe42c5f59035d054aa0dTerry Heo    public static final int EVENT_FIRST_FRAME_CAPTURED = 4;
509e922ca97097cb1aa67ff53219d874ea2503a80dWonsik Kim
51c22dbb69194c8e8fe2a32326d1f37a738cad0904Wonsik Kim    public interface Callback {
526e4cbfd2e5ffb739269e5e4affc2b6894bc4090eJae Seo        void onDeviceAvailable(TvInputHardwareInfo info, TvStreamConfig[] configs);
536e4cbfd2e5ffb739269e5e4affc2b6894bc4090eJae Seo        void onDeviceUnavailable(int deviceId);
546e4cbfd2e5ffb739269e5e4affc2b6894bc4090eJae Seo        void onStreamConfigurationChanged(int deviceId, TvStreamConfig[] configs);
556e4cbfd2e5ffb739269e5e4affc2b6894bc4090eJae Seo        void onFirstFrameCaptured(int deviceId, int streamId);
56c22dbb69194c8e8fe2a32326d1f37a738cad0904Wonsik Kim    }
57c22dbb69194c8e8fe2a32326d1f37a738cad0904Wonsik Kim
5857b37f610d33989f1b23e1b8d9e61fb177456364Wonsik Kim    private native long nativeOpen(MessageQueue queue);
59c22dbb69194c8e8fe2a32326d1f37a738cad0904Wonsik Kim
608f24a8b60f9afc1aedb89e7ee80ce65515439600Wonsik Kim    private static native int nativeAddOrUpdateStream(long ptr, int deviceId, int streamId,
61c22dbb69194c8e8fe2a32326d1f37a738cad0904Wonsik Kim            Surface surface);
62839ae5f460caadf8580b7e0ab77e255d7a1ddae5Wonsik Kim    private static native int nativeRemoveStream(long ptr, int deviceId, int streamId);
63c22dbb69194c8e8fe2a32326d1f37a738cad0904Wonsik Kim    private static native TvStreamConfig[] nativeGetStreamConfigs(long ptr, int deviceId,
64c22dbb69194c8e8fe2a32326d1f37a738cad0904Wonsik Kim            int generation);
65c22dbb69194c8e8fe2a32326d1f37a738cad0904Wonsik Kim    private static native void nativeClose(long ptr);
66c22dbb69194c8e8fe2a32326d1f37a738cad0904Wonsik Kim
675b1caaf7d8408bf0ce78d8d7a36f4649dda17797Jae Seo    private final Object mLock = new Object();
68610ccd9117fc1611fcc576d1cb1f717f1ef3fcbfWonsik Kim    private long mPtr = 0;
69c22dbb69194c8e8fe2a32326d1f37a738cad0904Wonsik Kim    private final Callback mCallback;
70c22dbb69194c8e8fe2a32326d1f37a738cad0904Wonsik Kim    private final Handler mHandler;
715b1caaf7d8408bf0ce78d8d7a36f4649dda17797Jae Seo    private final SparseIntArray mStreamConfigGenerations = new SparseIntArray();
725b1caaf7d8408bf0ce78d8d7a36f4649dda17797Jae Seo    private final SparseArray<TvStreamConfig[]> mStreamConfigs = new SparseArray<>();
73c22dbb69194c8e8fe2a32326d1f37a738cad0904Wonsik Kim
74c22dbb69194c8e8fe2a32326d1f37a738cad0904Wonsik Kim    public TvInputHal(Callback callback) {
75c22dbb69194c8e8fe2a32326d1f37a738cad0904Wonsik Kim        mCallback = callback;
76610ccd9117fc1611fcc576d1cb1f717f1ef3fcbfWonsik Kim        mHandler = new Handler(this);
77c22dbb69194c8e8fe2a32326d1f37a738cad0904Wonsik Kim    }
78c22dbb69194c8e8fe2a32326d1f37a738cad0904Wonsik Kim
7921aa3467cd14260418cc47334b656adf841a567cWonsik Kim    public void init() {
8021aa3467cd14260418cc47334b656adf841a567cWonsik Kim        synchronized (mLock) {
8157b37f610d33989f1b23e1b8d9e61fb177456364Wonsik Kim            mPtr = nativeOpen(mHandler.getLooper().getQueue());
8221aa3467cd14260418cc47334b656adf841a567cWonsik Kim        }
83c22dbb69194c8e8fe2a32326d1f37a738cad0904Wonsik Kim    }
84c22dbb69194c8e8fe2a32326d1f37a738cad0904Wonsik Kim
858f24a8b60f9afc1aedb89e7ee80ce65515439600Wonsik Kim    public int addOrUpdateStream(int deviceId, Surface surface, TvStreamConfig streamConfig) {
8621aa3467cd14260418cc47334b656adf841a567cWonsik Kim        synchronized (mLock) {
8721aa3467cd14260418cc47334b656adf841a567cWonsik Kim            if (mPtr == 0) {
8821aa3467cd14260418cc47334b656adf841a567cWonsik Kim                return ERROR_NO_INIT;
8921aa3467cd14260418cc47334b656adf841a567cWonsik Kim            }
9021aa3467cd14260418cc47334b656adf841a567cWonsik Kim            int generation = mStreamConfigGenerations.get(deviceId, 0);
9121aa3467cd14260418cc47334b656adf841a567cWonsik Kim            if (generation != streamConfig.getGeneration()) {
9221aa3467cd14260418cc47334b656adf841a567cWonsik Kim                return ERROR_STALE_CONFIG;
9321aa3467cd14260418cc47334b656adf841a567cWonsik Kim            }
948f24a8b60f9afc1aedb89e7ee80ce65515439600Wonsik Kim            if (nativeAddOrUpdateStream(mPtr, deviceId, streamConfig.getStreamId(), surface) == 0) {
9521aa3467cd14260418cc47334b656adf841a567cWonsik Kim                return SUCCESS;
9621aa3467cd14260418cc47334b656adf841a567cWonsik Kim            } else {
9721aa3467cd14260418cc47334b656adf841a567cWonsik Kim                return ERROR_UNKNOWN;
9821aa3467cd14260418cc47334b656adf841a567cWonsik Kim            }
99839ae5f460caadf8580b7e0ab77e255d7a1ddae5Wonsik Kim        }
100839ae5f460caadf8580b7e0ab77e255d7a1ddae5Wonsik Kim    }
101839ae5f460caadf8580b7e0ab77e255d7a1ddae5Wonsik Kim
10221aa3467cd14260418cc47334b656adf841a567cWonsik Kim    public int removeStream(int deviceId, TvStreamConfig streamConfig) {
10321aa3467cd14260418cc47334b656adf841a567cWonsik Kim        synchronized (mLock) {
10421aa3467cd14260418cc47334b656adf841a567cWonsik Kim            if (mPtr == 0) {
10521aa3467cd14260418cc47334b656adf841a567cWonsik Kim                return ERROR_NO_INIT;
10621aa3467cd14260418cc47334b656adf841a567cWonsik Kim            }
10721aa3467cd14260418cc47334b656adf841a567cWonsik Kim            int generation = mStreamConfigGenerations.get(deviceId, 0);
10821aa3467cd14260418cc47334b656adf841a567cWonsik Kim            if (generation != streamConfig.getGeneration()) {
10921aa3467cd14260418cc47334b656adf841a567cWonsik Kim                return ERROR_STALE_CONFIG;
11021aa3467cd14260418cc47334b656adf841a567cWonsik Kim            }
11121aa3467cd14260418cc47334b656adf841a567cWonsik Kim            if (nativeRemoveStream(mPtr, deviceId, streamConfig.getStreamId()) == 0) {
11221aa3467cd14260418cc47334b656adf841a567cWonsik Kim                return SUCCESS;
11321aa3467cd14260418cc47334b656adf841a567cWonsik Kim            } else {
11421aa3467cd14260418cc47334b656adf841a567cWonsik Kim                return ERROR_UNKNOWN;
11521aa3467cd14260418cc47334b656adf841a567cWonsik Kim            }
116c22dbb69194c8e8fe2a32326d1f37a738cad0904Wonsik Kim        }
117c22dbb69194c8e8fe2a32326d1f37a738cad0904Wonsik Kim    }
118c22dbb69194c8e8fe2a32326d1f37a738cad0904Wonsik Kim
11921aa3467cd14260418cc47334b656adf841a567cWonsik Kim    public void close() {
12021aa3467cd14260418cc47334b656adf841a567cWonsik Kim        synchronized (mLock) {
1212a2b299dca20b151d5dc5bda3d068d70e6f15f6cJae Seo            if (mPtr != 0L) {
12221aa3467cd14260418cc47334b656adf841a567cWonsik Kim                nativeClose(mPtr);
12321aa3467cd14260418cc47334b656adf841a567cWonsik Kim            }
124c22dbb69194c8e8fe2a32326d1f37a738cad0904Wonsik Kim        }
125c22dbb69194c8e8fe2a32326d1f37a738cad0904Wonsik Kim    }
126c22dbb69194c8e8fe2a32326d1f37a738cad0904Wonsik Kim
12721aa3467cd14260418cc47334b656adf841a567cWonsik Kim    private void retrieveStreamConfigsLocked(int deviceId) {
12821aa3467cd14260418cc47334b656adf841a567cWonsik Kim        int generation = mStreamConfigGenerations.get(deviceId, 0) + 1;
12921aa3467cd14260418cc47334b656adf841a567cWonsik Kim        mStreamConfigs.put(deviceId, nativeGetStreamConfigs(mPtr, deviceId, generation));
13021aa3467cd14260418cc47334b656adf841a567cWonsik Kim        mStreamConfigGenerations.put(deviceId, generation);
131c22dbb69194c8e8fe2a32326d1f37a738cad0904Wonsik Kim    }
132c22dbb69194c8e8fe2a32326d1f37a738cad0904Wonsik Kim
133c22dbb69194c8e8fe2a32326d1f37a738cad0904Wonsik Kim    // Called from native
1349e922ca97097cb1aa67ff53219d874ea2503a80dWonsik Kim    private void deviceAvailableFromNative(TvInputHardwareInfo info) {
135610ccd9117fc1611fcc576d1cb1f717f1ef3fcbfWonsik Kim        if (DEBUG) {
136610ccd9117fc1611fcc576d1cb1f717f1ef3fcbfWonsik Kim            Slog.d(TAG, "deviceAvailableFromNative: info = " + info);
137610ccd9117fc1611fcc576d1cb1f717f1ef3fcbfWonsik Kim        }
138610ccd9117fc1611fcc576d1cb1f717f1ef3fcbfWonsik Kim        mHandler.obtainMessage(EVENT_DEVICE_AVAILABLE, info).sendToTarget();
1399e922ca97097cb1aa67ff53219d874ea2503a80dWonsik Kim    }
1409e922ca97097cb1aa67ff53219d874ea2503a80dWonsik Kim
1419e922ca97097cb1aa67ff53219d874ea2503a80dWonsik Kim    private void deviceUnavailableFromNative(int deviceId) {
142610ccd9117fc1611fcc576d1cb1f717f1ef3fcbfWonsik Kim        mHandler.obtainMessage(EVENT_DEVICE_UNAVAILABLE, deviceId, 0).sendToTarget();
1439e922ca97097cb1aa67ff53219d874ea2503a80dWonsik Kim    }
1449e922ca97097cb1aa67ff53219d874ea2503a80dWonsik Kim
1459e922ca97097cb1aa67ff53219d874ea2503a80dWonsik Kim    private void streamConfigsChangedFromNative(int deviceId) {
146610ccd9117fc1611fcc576d1cb1f717f1ef3fcbfWonsik Kim        mHandler.obtainMessage(EVENT_STREAM_CONFIGURATION_CHANGED, deviceId, 0).sendToTarget();
1479e922ca97097cb1aa67ff53219d874ea2503a80dWonsik Kim    }
1489e922ca97097cb1aa67ff53219d874ea2503a80dWonsik Kim
149c086a3df3b28996cd10ebe42c5f59035d054aa0dTerry Heo    private void firstFrameCapturedFromNative(int deviceId, int streamId) {
150c086a3df3b28996cd10ebe42c5f59035d054aa0dTerry Heo        mHandler.sendMessage(
151c086a3df3b28996cd10ebe42c5f59035d054aa0dTerry Heo                mHandler.obtainMessage(EVENT_STREAM_CONFIGURATION_CHANGED, deviceId, streamId));
152c086a3df3b28996cd10ebe42c5f59035d054aa0dTerry Heo    }
153c086a3df3b28996cd10ebe42c5f59035d054aa0dTerry Heo
1549e922ca97097cb1aa67ff53219d874ea2503a80dWonsik Kim    // Handler.Callback implementation
1559e922ca97097cb1aa67ff53219d874ea2503a80dWonsik Kim
1566e4cbfd2e5ffb739269e5e4affc2b6894bc4090eJae Seo    private final Queue<Message> mPendingMessageQueue = new LinkedList<>();
1579e922ca97097cb1aa67ff53219d874ea2503a80dWonsik Kim
1589e922ca97097cb1aa67ff53219d874ea2503a80dWonsik Kim    @Override
1599e922ca97097cb1aa67ff53219d874ea2503a80dWonsik Kim    public boolean handleMessage(Message msg) {
1609e922ca97097cb1aa67ff53219d874ea2503a80dWonsik Kim        switch (msg.what) {
1619e922ca97097cb1aa67ff53219d874ea2503a80dWonsik Kim            case EVENT_DEVICE_AVAILABLE: {
16221aa3467cd14260418cc47334b656adf841a567cWonsik Kim                TvStreamConfig[] configs;
1639e922ca97097cb1aa67ff53219d874ea2503a80dWonsik Kim                TvInputHardwareInfo info = (TvInputHardwareInfo)msg.obj;
16421aa3467cd14260418cc47334b656adf841a567cWonsik Kim                synchronized (mLock) {
16521aa3467cd14260418cc47334b656adf841a567cWonsik Kim                    retrieveStreamConfigsLocked(info.getDeviceId());
16621aa3467cd14260418cc47334b656adf841a567cWonsik Kim                    if (DEBUG) {
16721aa3467cd14260418cc47334b656adf841a567cWonsik Kim                        Slog.d(TAG, "EVENT_DEVICE_AVAILABLE: info = " + info);
16821aa3467cd14260418cc47334b656adf841a567cWonsik Kim                    }
16921aa3467cd14260418cc47334b656adf841a567cWonsik Kim                    configs = mStreamConfigs.get(info.getDeviceId());
170610ccd9117fc1611fcc576d1cb1f717f1ef3fcbfWonsik Kim                }
17121aa3467cd14260418cc47334b656adf841a567cWonsik Kim                mCallback.onDeviceAvailable(info, configs);
1729e922ca97097cb1aa67ff53219d874ea2503a80dWonsik Kim                break;
173c22dbb69194c8e8fe2a32326d1f37a738cad0904Wonsik Kim            }
174c22dbb69194c8e8fe2a32326d1f37a738cad0904Wonsik Kim
1759e922ca97097cb1aa67ff53219d874ea2503a80dWonsik Kim            case EVENT_DEVICE_UNAVAILABLE: {
1769e922ca97097cb1aa67ff53219d874ea2503a80dWonsik Kim                int deviceId = msg.arg1;
177610ccd9117fc1611fcc576d1cb1f717f1ef3fcbfWonsik Kim                if (DEBUG) {
178610ccd9117fc1611fcc576d1cb1f717f1ef3fcbfWonsik Kim                    Slog.d(TAG, "EVENT_DEVICE_UNAVAILABLE: deviceId = " + deviceId);
179610ccd9117fc1611fcc576d1cb1f717f1ef3fcbfWonsik Kim                }
180d7c29189aa639bfac1e6efcd222e65c2c8ecf3f1Wonsik Kim                mCallback.onDeviceUnavailable(deviceId);
1819e922ca97097cb1aa67ff53219d874ea2503a80dWonsik Kim                break;
182c22dbb69194c8e8fe2a32326d1f37a738cad0904Wonsik Kim            }
183c22dbb69194c8e8fe2a32326d1f37a738cad0904Wonsik Kim
1849e922ca97097cb1aa67ff53219d874ea2503a80dWonsik Kim            case EVENT_STREAM_CONFIGURATION_CHANGED: {
18521aa3467cd14260418cc47334b656adf841a567cWonsik Kim                TvStreamConfig[] configs;
1869e922ca97097cb1aa67ff53219d874ea2503a80dWonsik Kim                int deviceId = msg.arg1;
18721aa3467cd14260418cc47334b656adf841a567cWonsik Kim                synchronized (mLock) {
18821aa3467cd14260418cc47334b656adf841a567cWonsik Kim                    if (DEBUG) {
18921aa3467cd14260418cc47334b656adf841a567cWonsik Kim                        Slog.d(TAG, "EVENT_STREAM_CONFIGURATION_CHANGED: deviceId = " + deviceId);
19021aa3467cd14260418cc47334b656adf841a567cWonsik Kim                    }
19121aa3467cd14260418cc47334b656adf841a567cWonsik Kim                    retrieveStreamConfigsLocked(deviceId);
19221aa3467cd14260418cc47334b656adf841a567cWonsik Kim                    configs = mStreamConfigs.get(deviceId);
193610ccd9117fc1611fcc576d1cb1f717f1ef3fcbfWonsik Kim                }
19421aa3467cd14260418cc47334b656adf841a567cWonsik Kim                mCallback.onStreamConfigurationChanged(deviceId, configs);
1959e922ca97097cb1aa67ff53219d874ea2503a80dWonsik Kim                break;
196c22dbb69194c8e8fe2a32326d1f37a738cad0904Wonsik Kim            }
1979e922ca97097cb1aa67ff53219d874ea2503a80dWonsik Kim
198c086a3df3b28996cd10ebe42c5f59035d054aa0dTerry Heo            case EVENT_FIRST_FRAME_CAPTURED: {
199c086a3df3b28996cd10ebe42c5f59035d054aa0dTerry Heo                int deviceId = msg.arg1;
200c086a3df3b28996cd10ebe42c5f59035d054aa0dTerry Heo                int streamId = msg.arg2;
201c086a3df3b28996cd10ebe42c5f59035d054aa0dTerry Heo                mCallback.onFirstFrameCaptured(deviceId, streamId);
202c086a3df3b28996cd10ebe42c5f59035d054aa0dTerry Heo                break;
203c086a3df3b28996cd10ebe42c5f59035d054aa0dTerry Heo            }
204c086a3df3b28996cd10ebe42c5f59035d054aa0dTerry Heo
2059e922ca97097cb1aa67ff53219d874ea2503a80dWonsik Kim            default:
2069e922ca97097cb1aa67ff53219d874ea2503a80dWonsik Kim                Slog.e(TAG, "Unknown event: " + msg);
207610ccd9117fc1611fcc576d1cb1f717f1ef3fcbfWonsik Kim                return false;
2089e922ca97097cb1aa67ff53219d874ea2503a80dWonsik Kim        }
209610ccd9117fc1611fcc576d1cb1f717f1ef3fcbfWonsik Kim
210610ccd9117fc1611fcc576d1cb1f717f1ef3fcbfWonsik Kim        return true;
211c22dbb69194c8e8fe2a32326d1f37a738cad0904Wonsik Kim    }
212c22dbb69194c8e8fe2a32326d1f37a738cad0904Wonsik Kim}
213