11abddd9f6225298066094e20a6c29061b6af4590Nick Chalko/* 21abddd9f6225298066094e20a6c29061b6af4590Nick Chalko * Copyright (C) 2015 The Android Open Source Project 31abddd9f6225298066094e20a6c29061b6af4590Nick Chalko * 41abddd9f6225298066094e20a6c29061b6af4590Nick Chalko * Licensed under the Apache License, Version 2.0 (the "License"); 51abddd9f6225298066094e20a6c29061b6af4590Nick Chalko * you may not use this file except in compliance with the License. 61abddd9f6225298066094e20a6c29061b6af4590Nick Chalko * You may obtain a copy of the License at 71abddd9f6225298066094e20a6c29061b6af4590Nick Chalko * 81abddd9f6225298066094e20a6c29061b6af4590Nick Chalko * http://www.apache.org/licenses/LICENSE-2.0 91abddd9f6225298066094e20a6c29061b6af4590Nick Chalko * 101abddd9f6225298066094e20a6c29061b6af4590Nick Chalko * Unless required by applicable law or agreed to in writing, software 111abddd9f6225298066094e20a6c29061b6af4590Nick Chalko * distributed under the License is distributed on an "AS IS" BASIS, 121abddd9f6225298066094e20a6c29061b6af4590Nick Chalko * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 131abddd9f6225298066094e20a6c29061b6af4590Nick Chalko * See the License for the specific language governing permissions and 141abddd9f6225298066094e20a6c29061b6af4590Nick Chalko * limitations under the License. 151abddd9f6225298066094e20a6c29061b6af4590Nick Chalko */ 161abddd9f6225298066094e20a6c29061b6af4590Nick Chalko 1765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkopackage com.android.tv.tuner; 181abddd9f6225298066094e20a6c29061b6af4590Nick Chalko 191abddd9f6225298066094e20a6c29061b6af4590Nick Chalkoimport android.content.Context; 201abddd9f6225298066094e20a6c29061b6af4590Nick Chalkoimport android.media.tv.TvInputManager; 211abddd9f6225298066094e20a6c29061b6af4590Nick Chalkoimport android.os.ParcelFileDescriptor; 221abddd9f6225298066094e20a6c29061b6af4590Nick Chalkoimport android.support.annotation.IntDef; 231abddd9f6225298066094e20a6c29061b6af4590Nick Chalkoimport android.support.annotation.NonNull; 241abddd9f6225298066094e20a6c29061b6af4590Nick Chalkoimport android.util.Log; 251abddd9f6225298066094e20a6c29061b6af4590Nick Chalko 26ba5845f23b8fbc985890f892961abc8b39886611Nick Chalkoimport com.android.tv.common.recording.RecordingCapability; 2765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport com.android.tv.tuner.tvinput.TunerTvInputService; 28ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko 291abddd9f6225298066094e20a6c29061b6af4590Nick Chalkoimport java.lang.annotation.Retention; 301abddd9f6225298066094e20a6c29061b6af4590Nick Chalkoimport java.lang.annotation.RetentionPolicy; 311abddd9f6225298066094e20a6c29061b6af4590Nick Chalkoimport java.lang.reflect.InvocationTargetException; 321abddd9f6225298066094e20a6c29061b6af4590Nick Chalkoimport java.lang.reflect.Method; 331abddd9f6225298066094e20a6c29061b6af4590Nick Chalkoimport java.util.ArrayList; 341abddd9f6225298066094e20a6c29061b6af4590Nick Chalkoimport java.util.Collections; 351abddd9f6225298066094e20a6c29061b6af4590Nick Chalkoimport java.util.List; 3665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport java.util.Locale; 371abddd9f6225298066094e20a6c29061b6af4590Nick Chalko 381abddd9f6225298066094e20a6c29061b6af4590Nick Chalko/** 391abddd9f6225298066094e20a6c29061b6af4590Nick Chalko * Provides with the file descriptors to access DVB device. 401abddd9f6225298066094e20a6c29061b6af4590Nick Chalko */ 411abddd9f6225298066094e20a6c29061b6af4590Nick Chalkopublic class DvbDeviceAccessor { 421abddd9f6225298066094e20a6c29061b6af4590Nick Chalko private static final String TAG = "DvbDeviceAccessor"; 431abddd9f6225298066094e20a6c29061b6af4590Nick Chalko 441abddd9f6225298066094e20a6c29061b6af4590Nick Chalko @IntDef({DVB_DEVICE_DEMUX, DVB_DEVICE_DVR, DVB_DEVICE_FRONTEND}) 451abddd9f6225298066094e20a6c29061b6af4590Nick Chalko @Retention(RetentionPolicy.SOURCE) 461abddd9f6225298066094e20a6c29061b6af4590Nick Chalko public @interface DvbDevice {} 471abddd9f6225298066094e20a6c29061b6af4590Nick Chalko public static final int DVB_DEVICE_DEMUX = 0; // TvInputManager.DVB_DEVICE_DEMUX; 481abddd9f6225298066094e20a6c29061b6af4590Nick Chalko public static final int DVB_DEVICE_DVR = 1; // TvInputManager.DVB_DEVICE_DVR; 491abddd9f6225298066094e20a6c29061b6af4590Nick Chalko public static final int DVB_DEVICE_FRONTEND = 2; // TvInputManager.DVB_DEVICE_FRONTEND; 501abddd9f6225298066094e20a6c29061b6af4590Nick Chalko 511abddd9f6225298066094e20a6c29061b6af4590Nick Chalko private static Method sGetDvbDeviceListMethod; 521abddd9f6225298066094e20a6c29061b6af4590Nick Chalko private static Method sOpenDvbDeviceMethod; 531abddd9f6225298066094e20a6c29061b6af4590Nick Chalko 541abddd9f6225298066094e20a6c29061b6af4590Nick Chalko private final TvInputManager mTvInputManager; 551abddd9f6225298066094e20a6c29061b6af4590Nick Chalko 561abddd9f6225298066094e20a6c29061b6af4590Nick Chalko static { 571abddd9f6225298066094e20a6c29061b6af4590Nick Chalko try { 581abddd9f6225298066094e20a6c29061b6af4590Nick Chalko Class tvInputManagerClass = Class.forName("android.media.tv.TvInputManager"); 591abddd9f6225298066094e20a6c29061b6af4590Nick Chalko Class dvbDeviceInfoClass = Class.forName("android.media.tv.DvbDeviceInfo"); 601abddd9f6225298066094e20a6c29061b6af4590Nick Chalko sGetDvbDeviceListMethod = tvInputManagerClass.getDeclaredMethod("getDvbDeviceList"); 611abddd9f6225298066094e20a6c29061b6af4590Nick Chalko sGetDvbDeviceListMethod.setAccessible(true); 621abddd9f6225298066094e20a6c29061b6af4590Nick Chalko sOpenDvbDeviceMethod = tvInputManagerClass.getDeclaredMethod( 631abddd9f6225298066094e20a6c29061b6af4590Nick Chalko "openDvbDevice", dvbDeviceInfoClass, Integer.TYPE); 641abddd9f6225298066094e20a6c29061b6af4590Nick Chalko sOpenDvbDeviceMethod.setAccessible(true); 651abddd9f6225298066094e20a6c29061b6af4590Nick Chalko } catch (ClassNotFoundException e) { 661abddd9f6225298066094e20a6c29061b6af4590Nick Chalko Log.e(TAG, "Couldn't find class", e); 671abddd9f6225298066094e20a6c29061b6af4590Nick Chalko } catch (NoSuchMethodException e) { 681abddd9f6225298066094e20a6c29061b6af4590Nick Chalko Log.e(TAG, "Couldn't find method", e); 691abddd9f6225298066094e20a6c29061b6af4590Nick Chalko } 701abddd9f6225298066094e20a6c29061b6af4590Nick Chalko } 711abddd9f6225298066094e20a6c29061b6af4590Nick Chalko 721abddd9f6225298066094e20a6c29061b6af4590Nick Chalko public DvbDeviceAccessor(Context context) { 7365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko mTvInputManager = (TvInputManager) context.getSystemService(Context.TV_INPUT_SERVICE); 741abddd9f6225298066094e20a6c29061b6af4590Nick Chalko } 751abddd9f6225298066094e20a6c29061b6af4590Nick Chalko 761abddd9f6225298066094e20a6c29061b6af4590Nick Chalko public List<DvbDeviceInfoWrapper> getDvbDeviceList() { 771abddd9f6225298066094e20a6c29061b6af4590Nick Chalko try { 781abddd9f6225298066094e20a6c29061b6af4590Nick Chalko List<DvbDeviceInfoWrapper> wrapperList = new ArrayList<>(); 791abddd9f6225298066094e20a6c29061b6af4590Nick Chalko List dvbDeviceInfoList = (List) sGetDvbDeviceListMethod.invoke(mTvInputManager); 801abddd9f6225298066094e20a6c29061b6af4590Nick Chalko for (Object dvbDeviceInfo : dvbDeviceInfoList) { 811abddd9f6225298066094e20a6c29061b6af4590Nick Chalko wrapperList.add(new DvbDeviceInfoWrapper(dvbDeviceInfo)); 821abddd9f6225298066094e20a6c29061b6af4590Nick Chalko } 831abddd9f6225298066094e20a6c29061b6af4590Nick Chalko Collections.sort(wrapperList); 841abddd9f6225298066094e20a6c29061b6af4590Nick Chalko return wrapperList; 851abddd9f6225298066094e20a6c29061b6af4590Nick Chalko } catch (IllegalAccessException e) { 861abddd9f6225298066094e20a6c29061b6af4590Nick Chalko Log.e(TAG, "Couldn't access", e); 871abddd9f6225298066094e20a6c29061b6af4590Nick Chalko } catch (InvocationTargetException e) { 881abddd9f6225298066094e20a6c29061b6af4590Nick Chalko Log.e(TAG, "Couldn't invoke", e); 891abddd9f6225298066094e20a6c29061b6af4590Nick Chalko } 901abddd9f6225298066094e20a6c29061b6af4590Nick Chalko return null; 911abddd9f6225298066094e20a6c29061b6af4590Nick Chalko } 921abddd9f6225298066094e20a6c29061b6af4590Nick Chalko 9365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko /** 9465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko * Returns the number of currently connected DVB devices. 9565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko */ 9665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko public int getNumOfDvbDevices() { 9765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko List<DvbDeviceInfoWrapper> dvbDeviceList = getDvbDeviceList(); 9865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko return dvbDeviceList == null ? 0 : dvbDeviceList.size(); 9965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 10065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 1011abddd9f6225298066094e20a6c29061b6af4590Nick Chalko public boolean isDvbDeviceAvailable() { 1021abddd9f6225298066094e20a6c29061b6af4590Nick Chalko try { 1031abddd9f6225298066094e20a6c29061b6af4590Nick Chalko List dvbDeviceInfoList = (List) sGetDvbDeviceListMethod.invoke(mTvInputManager); 1041abddd9f6225298066094e20a6c29061b6af4590Nick Chalko return (!dvbDeviceInfoList.isEmpty()); 1051abddd9f6225298066094e20a6c29061b6af4590Nick Chalko } catch (IllegalAccessException e) { 1061abddd9f6225298066094e20a6c29061b6af4590Nick Chalko Log.e(TAG, "Couldn't access", e); 1071abddd9f6225298066094e20a6c29061b6af4590Nick Chalko } catch (InvocationTargetException e) { 1081abddd9f6225298066094e20a6c29061b6af4590Nick Chalko Log.e(TAG, "Couldn't invoke", e); 1091abddd9f6225298066094e20a6c29061b6af4590Nick Chalko } 1101abddd9f6225298066094e20a6c29061b6af4590Nick Chalko return false; 1111abddd9f6225298066094e20a6c29061b6af4590Nick Chalko } 1121abddd9f6225298066094e20a6c29061b6af4590Nick Chalko 1131abddd9f6225298066094e20a6c29061b6af4590Nick Chalko public ParcelFileDescriptor openDvbDevice(DvbDeviceInfoWrapper deviceInfo, 1141abddd9f6225298066094e20a6c29061b6af4590Nick Chalko @DvbDevice int device) { 1151abddd9f6225298066094e20a6c29061b6af4590Nick Chalko try { 1161abddd9f6225298066094e20a6c29061b6af4590Nick Chalko return (ParcelFileDescriptor) sOpenDvbDeviceMethod.invoke( 1171abddd9f6225298066094e20a6c29061b6af4590Nick Chalko mTvInputManager, deviceInfo.getDvbDeviceInfo(), device); 1181abddd9f6225298066094e20a6c29061b6af4590Nick Chalko } catch (IllegalAccessException e) { 1191abddd9f6225298066094e20a6c29061b6af4590Nick Chalko Log.e(TAG, "Couldn't access", e); 1201abddd9f6225298066094e20a6c29061b6af4590Nick Chalko } catch (InvocationTargetException e) { 1211abddd9f6225298066094e20a6c29061b6af4590Nick Chalko Log.e(TAG, "Couldn't invoke", e); 1221abddd9f6225298066094e20a6c29061b6af4590Nick Chalko } 1231abddd9f6225298066094e20a6c29061b6af4590Nick Chalko return null; 1241abddd9f6225298066094e20a6c29061b6af4590Nick Chalko } 1251abddd9f6225298066094e20a6c29061b6af4590Nick Chalko 126ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko /** 127ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko * Returns the current recording capability for USB tuner. 128ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko * @param inputId the input id to use. 129ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko */ 130ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko public RecordingCapability getRecordingCapability(String inputId) { 131ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko List<DvbDeviceInfoWrapper> deviceList = getDvbDeviceList(); 132ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko // TODO(DVR) implement accurate capabilities and updating values when needed. 133ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko return RecordingCapability.builder() 134ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko .setInputId(inputId) 135ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko .setMaxConcurrentPlayingSessions(1) 136ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko .setMaxConcurrentTunedSessions(deviceList.size()) 137ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko .setMaxConcurrentSessionsOfAllTypes(deviceList.size() + 1) 138ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko .build(); 139ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko } 140ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko 1411abddd9f6225298066094e20a6c29061b6af4590Nick Chalko public static class DvbDeviceInfoWrapper implements Comparable<DvbDeviceInfoWrapper> { 1421abddd9f6225298066094e20a6c29061b6af4590Nick Chalko private static Method sGetAdapterIdMethod; 1431abddd9f6225298066094e20a6c29061b6af4590Nick Chalko private static Method sGetDeviceIdMethod; 1441abddd9f6225298066094e20a6c29061b6af4590Nick Chalko private final Object mDvbDeviceInfo; 145ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko private final int mAdapterId; 146ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko private final int mDeviceId; 147ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko private final long mId; 1481abddd9f6225298066094e20a6c29061b6af4590Nick Chalko 1491abddd9f6225298066094e20a6c29061b6af4590Nick Chalko static { 1501abddd9f6225298066094e20a6c29061b6af4590Nick Chalko try { 1511abddd9f6225298066094e20a6c29061b6af4590Nick Chalko Class dvbDeviceInfoClass = Class.forName("android.media.tv.DvbDeviceInfo"); 1521abddd9f6225298066094e20a6c29061b6af4590Nick Chalko sGetAdapterIdMethod = dvbDeviceInfoClass.getDeclaredMethod("getAdapterId"); 1531abddd9f6225298066094e20a6c29061b6af4590Nick Chalko sGetAdapterIdMethod.setAccessible(true); 1541abddd9f6225298066094e20a6c29061b6af4590Nick Chalko sGetDeviceIdMethod = dvbDeviceInfoClass.getDeclaredMethod("getDeviceId"); 1551abddd9f6225298066094e20a6c29061b6af4590Nick Chalko sGetDeviceIdMethod.setAccessible(true); 1561abddd9f6225298066094e20a6c29061b6af4590Nick Chalko } catch (ClassNotFoundException e) { 1571abddd9f6225298066094e20a6c29061b6af4590Nick Chalko Log.e(TAG, "Couldn't find class", e); 1581abddd9f6225298066094e20a6c29061b6af4590Nick Chalko } catch (NoSuchMethodException e) { 1591abddd9f6225298066094e20a6c29061b6af4590Nick Chalko Log.e(TAG, "Couldn't find method", e); 1601abddd9f6225298066094e20a6c29061b6af4590Nick Chalko } 1611abddd9f6225298066094e20a6c29061b6af4590Nick Chalko } 1621abddd9f6225298066094e20a6c29061b6af4590Nick Chalko 1631abddd9f6225298066094e20a6c29061b6af4590Nick Chalko public DvbDeviceInfoWrapper(Object dvbDeviceInfo) { 1641abddd9f6225298066094e20a6c29061b6af4590Nick Chalko mDvbDeviceInfo = dvbDeviceInfo; 165ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko mAdapterId = initAdapterId(); 166ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko mDeviceId = initDeviceId(); 167ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko mId = (((long) getAdapterId()) << 32) | (getDeviceId() & 0xffffffffL); 1681abddd9f6225298066094e20a6c29061b6af4590Nick Chalko } 1691abddd9f6225298066094e20a6c29061b6af4590Nick Chalko 1701abddd9f6225298066094e20a6c29061b6af4590Nick Chalko public long getId() { 171ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko return mId; 1721abddd9f6225298066094e20a6c29061b6af4590Nick Chalko } 1731abddd9f6225298066094e20a6c29061b6af4590Nick Chalko 1741abddd9f6225298066094e20a6c29061b6af4590Nick Chalko public int getAdapterId() { 175ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko return mAdapterId; 176ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko } 177ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko 178ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko private int initAdapterId() { 1791abddd9f6225298066094e20a6c29061b6af4590Nick Chalko try { 1801abddd9f6225298066094e20a6c29061b6af4590Nick Chalko return (int) sGetAdapterIdMethod.invoke(mDvbDeviceInfo); 1811abddd9f6225298066094e20a6c29061b6af4590Nick Chalko } catch (InvocationTargetException e) { 1821abddd9f6225298066094e20a6c29061b6af4590Nick Chalko Log.e(TAG, "Couldn't invoke", e); 1831abddd9f6225298066094e20a6c29061b6af4590Nick Chalko } catch (IllegalAccessException e) { 1841abddd9f6225298066094e20a6c29061b6af4590Nick Chalko Log.e(TAG, "Couldn't access", e); 1851abddd9f6225298066094e20a6c29061b6af4590Nick Chalko } 1861abddd9f6225298066094e20a6c29061b6af4590Nick Chalko return -1; 1871abddd9f6225298066094e20a6c29061b6af4590Nick Chalko } 1881abddd9f6225298066094e20a6c29061b6af4590Nick Chalko 1891abddd9f6225298066094e20a6c29061b6af4590Nick Chalko public int getDeviceId() { 190ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko return mDeviceId; 191ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko } 192ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko 193ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko private int initDeviceId() { 1941abddd9f6225298066094e20a6c29061b6af4590Nick Chalko try { 1951abddd9f6225298066094e20a6c29061b6af4590Nick Chalko return (int) sGetDeviceIdMethod.invoke(mDvbDeviceInfo); 1961abddd9f6225298066094e20a6c29061b6af4590Nick Chalko } catch (InvocationTargetException e) { 1971abddd9f6225298066094e20a6c29061b6af4590Nick Chalko Log.e(TAG, "Couldn't invoke", e); 1981abddd9f6225298066094e20a6c29061b6af4590Nick Chalko } catch (IllegalAccessException e) { 1991abddd9f6225298066094e20a6c29061b6af4590Nick Chalko Log.e(TAG, "Couldn't access", e); 2001abddd9f6225298066094e20a6c29061b6af4590Nick Chalko } 2011abddd9f6225298066094e20a6c29061b6af4590Nick Chalko return -1; 2021abddd9f6225298066094e20a6c29061b6af4590Nick Chalko } 2031abddd9f6225298066094e20a6c29061b6af4590Nick Chalko 2041abddd9f6225298066094e20a6c29061b6af4590Nick Chalko public Object getDvbDeviceInfo() { 2051abddd9f6225298066094e20a6c29061b6af4590Nick Chalko return mDvbDeviceInfo; 2061abddd9f6225298066094e20a6c29061b6af4590Nick Chalko } 2071abddd9f6225298066094e20a6c29061b6af4590Nick Chalko 2081abddd9f6225298066094e20a6c29061b6af4590Nick Chalko @Override 2091abddd9f6225298066094e20a6c29061b6af4590Nick Chalko public int compareTo(@NonNull DvbDeviceInfoWrapper another) { 2101abddd9f6225298066094e20a6c29061b6af4590Nick Chalko if (getAdapterId() != another.getAdapterId()) { 2111abddd9f6225298066094e20a6c29061b6af4590Nick Chalko return getAdapterId() - another.getAdapterId(); 2121abddd9f6225298066094e20a6c29061b6af4590Nick Chalko } 2131abddd9f6225298066094e20a6c29061b6af4590Nick Chalko return getDeviceId() - another.getDeviceId(); 2141abddd9f6225298066094e20a6c29061b6af4590Nick Chalko } 2151abddd9f6225298066094e20a6c29061b6af4590Nick Chalko 2161abddd9f6225298066094e20a6c29061b6af4590Nick Chalko @Override 2171abddd9f6225298066094e20a6c29061b6af4590Nick Chalko public String toString() { 21865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko return String.format(Locale.US, "DvbDeviceInfo {adapterId: %d, deviceId: %d}", 21965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko getAdapterId(), 2201abddd9f6225298066094e20a6c29061b6af4590Nick Chalko getDeviceId()); 2211abddd9f6225298066094e20a6c29061b6af4590Nick Chalko } 2221abddd9f6225298066094e20a6c29061b6af4590Nick Chalko } 2231abddd9f6225298066094e20a6c29061b6af4590Nick Chalko} 224