1a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev/* 2a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev * Copyright (C) 2015 The Android Open Source Project 3a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev * 4a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev * Licensed under the Apache License, Version 2.0 (the "License"); 5a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev * you may not use this file except in compliance with the License. 6a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev * You may obtain a copy of the License at 7a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev * 8a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev * http://www.apache.org/licenses/LICENSE-2.0 9a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev * 10a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev * Unless required by applicable law or agreed to in writing, software 11a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev * distributed under the License is distributed on an "AS IS" BASIS, 12a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev * See the License for the specific language governing permissions and 14a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev * limitations under the License. 15a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev */ 16a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev 17a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheevpackage android.support.v7.util; 18a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev 190a017072206f93474ccd2706e7983c2ff778b904Yigit Boyarimport org.junit.Before; 200a017072206f93474ccd2706e7983c2ff778b904Yigit Boyarimport org.junit.Test; 210a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar 22a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheevimport static org.hamcrest.MatcherAssert.*; 23a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheevimport static org.hamcrest.CoreMatchers.*; 24a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev 25a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheevimport android.os.Looper; 26de8e2baf9504defe12972fbf60935a1148f1098fVladislav Kaznacheevimport android.support.annotation.UiThread; 27f1b288ec2104488f4a92e911b0ab80c8f0f3e9d1Yigit Boyarimport android.test.suitebuilder.annotation.MediumTest; 28a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev 29a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheevimport java.util.HashMap; 30a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheevimport java.util.Map; 31a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheevimport java.util.concurrent.Semaphore; 32a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheevimport java.util.concurrent.TimeUnit; 33a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev 34f1b288ec2104488f4a92e911b0ab80c8f0f3e9d1Yigit Boyar@MediumTest 35de8e2baf9504defe12972fbf60935a1148f1098fVladislav Kaznacheevpublic class ThreadUtilTest extends BaseThreadedTest { 36a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev Map<String, LockedObject> results = new HashMap<>(); 37a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev 38a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev ThreadUtil.MainThreadCallback<Integer> mMainThreadProxy; 39a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev ThreadUtil.BackgroundCallback<Integer> mBackgroundProxy; 40a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev 410a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar @Before 420a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar public void init() throws Exception { 430a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar super.setUp(); 440a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar } 450a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar 46de8e2baf9504defe12972fbf60935a1148f1098fVladislav Kaznacheev @Override 47de8e2baf9504defe12972fbf60935a1148f1098fVladislav Kaznacheev @UiThread 48de8e2baf9504defe12972fbf60935a1148f1098fVladislav Kaznacheev public void setUpUi() { 49a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev ThreadUtil<Integer> threadUtil = new MessageThreadUtil<>(); 50a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev 51a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev mMainThreadProxy = threadUtil.getMainThreadProxy( 52a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev new ThreadUtil.MainThreadCallback<Integer>() { 53a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev @Override 54a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev public void updateItemCount(int generation, int itemCount) { 55a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev assertMainThread(); 56a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev setResultData("updateItemCount", generation, itemCount); 57a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev } 58a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev 59a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev @Override 60a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev public void addTile(int generation, TileList.Tile<Integer> data) { 61a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev assertMainThread(); 62a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev setResultData("addTile", generation, data); 63a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev } 64a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev 65a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev @Override 66a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev public void removeTile(int generation, int position) { 67a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev assertMainThread(); 68a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev setResultData("removeTile", generation, position); 69a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev } 70a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev }); 71a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev 72a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev mBackgroundProxy = threadUtil.getBackgroundProxy( 73a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev new ThreadUtil.BackgroundCallback<Integer>() { 74a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev @Override 75a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev public void refresh(int generation) { 76a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev assertBackgroundThread(); 77a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev setResultData("refresh", generation); 78a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev } 79a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev 80a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev @Override 81a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev public void updateRange(int rangeStart, int rangeEnd, int extRangeStart, 82a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev int extRangeEnd, int scrollHint) { 83a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev assertBackgroundThread(); 84a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev setResultData("updateRange", rangeStart, rangeEnd, 85a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev extRangeStart, extRangeEnd, scrollHint); 86a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev } 87a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev 88a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev @Override 89a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev public void loadTile(int position, int scrollHint) { 90a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev assertBackgroundThread(); 91a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev setResultData("loadTile", position, scrollHint); 92a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev } 93a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev 94a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev @Override 95a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev public void recycleTile(TileList.Tile<Integer> data) { 96a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev assertBackgroundThread(); 97a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev setResultData("recycleTile", data); 98a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev } 99a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev }); 100a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev } 101a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev 1020a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar @Test 1030a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar public void updateItemCount() throws InterruptedException { 104a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev initWait("updateItemCount"); 105de8e2baf9504defe12972fbf60935a1148f1098fVladislav Kaznacheev // In this test and below the calls to mMainThreadProxy are not really made from the UI 106de8e2baf9504defe12972fbf60935a1148f1098fVladislav Kaznacheev // thread. That's OK since the message queue inside mMainThreadProxy is synchronized. 107a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev mMainThreadProxy.updateItemCount(7, 9); 108a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev Object[] data = waitFor("updateItemCount"); 109a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev assertThat(data, is(new Object[]{7, 9})); 110a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev } 111a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev 1120a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar @Test 1130a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar public void addTile() throws InterruptedException { 114a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev initWait("addTile"); 115a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev TileList.Tile<Integer> tile = new TileList.Tile<Integer>(Integer.class, 10); 116a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev mMainThreadProxy.addTile(3, tile); 117a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev Object[] data = waitFor("addTile"); 118a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev assertThat(data, is(new Object[]{3, tile})); 119a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev } 120a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev 1210a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar @Test 1220a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar public void removeTile() throws InterruptedException { 123a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev initWait("removeTile"); 124a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev mMainThreadProxy.removeTile(1, 2); 125a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev Object[] data = waitFor("removeTile"); 126a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev assertThat(data, is(new Object[]{1, 2})); 127a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev } 128a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev 1290a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar @Test 1300a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar public void refresh() throws InterruptedException { 131a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev initWait("refresh"); 132de8e2baf9504defe12972fbf60935a1148f1098fVladislav Kaznacheev // In this test and below the calls to mBackgroundProxy are not really made from the worker 133de8e2baf9504defe12972fbf60935a1148f1098fVladislav Kaznacheev // thread. That's OK since the message queue inside mBackgroundProxy is synchronized. 134a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev mBackgroundProxy.refresh(2); 135a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev Object[] data = waitFor("refresh"); 136a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev assertThat(data, is(new Object[]{2})); 137a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev } 138a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev 1390a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar @Test 1400a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar public void rangeUpdate() throws InterruptedException { 141a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev initWait("updateRange"); 142a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev mBackgroundProxy.updateRange(10, 20, 5, 25, 1); 143a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev Object[] data = waitFor("updateRange"); 144a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev assertThat(data, is(new Object[] {10, 20, 5, 25, 1})); 145a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev } 146a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev 1470a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar @Test 1480a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar public void loadTile() throws InterruptedException { 149a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev initWait("loadTile"); 150a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev mBackgroundProxy.loadTile(2, 1); 151a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev Object[] data = waitFor("loadTile"); 152a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev assertThat(data, is(new Object[]{2, 1})); 153a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev } 154a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev 1550a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar @Test 1560a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar public void recycleTile() throws InterruptedException { 157a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev initWait("recycleTile"); 158a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev TileList.Tile<Integer> tile = new TileList.Tile<Integer>(Integer.class, 10); 159a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev mBackgroundProxy.recycleTile(tile); 160a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev Object[] data = waitFor("recycleTile"); 161a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev assertThat(data, is(new Object[]{tile})); 162a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev } 163a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev 164a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev private void assertMainThread() { 165a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev assertThat(Looper.myLooper(), notNullValue()); 166a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev assertThat(Looper.myLooper(), sameInstance(Looper.getMainLooper())); 167a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev } 168a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev 169a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev private void assertBackgroundThread() { 170a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev assertThat(Looper.myLooper(), not(Looper.getMainLooper())); 171a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev } 172a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev 173a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev private void initWait(String key) throws InterruptedException { 174a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev results.put(key, new LockedObject()); 175a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev } 176a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev 177a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev private Object[] waitFor(String key) throws InterruptedException { 178a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev return results.get(key).waitFor(); 179a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev } 180a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev 181a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev private void setResultData(String key, Object... args) { 182a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev if (results.containsKey(key)) { 183a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev results.get(key).set(args); 184a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev } 185a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev } 186a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev 187a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev private class LockedObject { 188a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev private Semaphore mLock = new Semaphore(1); 189a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev private volatile Object[] mArgs; 190a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev 191a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev public LockedObject() { 192a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev mLock.drainPermits(); 193a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev } 194a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev 195a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev public void set(Object... args) { 196a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev mArgs = args; 197a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev mLock.release(1); 198a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev } 199a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev 200a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev public Object[] waitFor() throws InterruptedException { 201a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev mLock.tryAcquire(1, 2, TimeUnit.SECONDS); 202a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev return mArgs; 203a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev } 204a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev } 205a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev} 206