19dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel/*
29dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel * Copyright (C) 2015 The Android Open Source Project
39dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel *
49dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel * Licensed under the Apache License, Version 2.0 (the "License");
59dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel * you may not use this file except in compliance with the License.
69dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel * You may obtain a copy of the License at
79dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel *
89dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel *      http://www.apache.org/licenses/LICENSE-2.0
99dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel *
109dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel * Unless required by applicable law or agreed to in writing, software
119dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel * distributed under the License is distributed on an "AS IS" BASIS,
129dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
139dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel * See the License for the specific language governing permissions and
149dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel * limitations under the License.
159dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel */
169dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmelpackage android.surfacecomposition;
179dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel
189dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmelimport java.text.DecimalFormat;
199dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmelimport java.util.ArrayList;
209dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmelimport java.util.List;
219dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel
229dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmelimport android.app.ActionBar;
239dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmelimport android.app.Activity;
249dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmelimport android.app.ActivityManager;
259dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmelimport android.app.ActivityManager.MemoryInfo;
269dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmelimport android.content.Context;
277578d10f6cb2203ba3575b82b4b67e3a5d448bd7Yury Khmelimport android.content.pm.PackageManager;
289dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmelimport android.graphics.Color;
299dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmelimport android.graphics.PixelFormat;
309dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmelimport android.graphics.Rect;
319dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmelimport android.graphics.drawable.ColorDrawable;
329dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmelimport android.os.Bundle;
339dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmelimport android.view.Display;
349dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmelimport android.view.View;
359dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmelimport android.view.View.OnClickListener;
369dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmelimport android.view.ViewGroup;
379dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmelimport android.view.Window;
389dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmelimport android.view.WindowManager;
399dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmelimport android.widget.ArrayAdapter;
409dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmelimport android.widget.Button;
419dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmelimport android.widget.LinearLayout;
429dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmelimport android.widget.RelativeLayout;
439dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmelimport android.widget.Spinner;
449dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmelimport android.widget.TextView;
459dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel
469dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel/**
479dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel * This activity is designed to measure peformance scores of Android surfaces.
489dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel * It can work in two modes. In first mode functionality of this activity is
499dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel * invoked from Cts test (SurfaceCompositionTest). This activity can also be
509dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel * used in manual mode as a normal app. Different pixel formats are supported.
519dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel *
529dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel * measureCompositionScore(pixelFormat)
539dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel *   This test measures surface compositor performance which shows how many
549dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel *   surfaces of specific format surface compositor can combine without dropping
559dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel *   frames. We allow one dropped frame per half second.
569dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel *
579dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel * measureAllocationScore(pixelFormat)
589dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel *   This test measures surface allocation/deallocation performance. It shows
599dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel *   how many surface lifecycles (creation, destruction) can be done per second.
609dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel *
619dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel * In manual mode, which activated by pressing button 'Compositor speed' or
629dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel * 'Allocator speed', all possible pixel format are tested and combined result
639dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel * is displayed in text view. Additional system information such as memory
649dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel * status, display size and surface format is also displayed and regulary
659dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel * updated.
669dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel */
679dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmelpublic class SurfaceCompositionMeasuringActivity extends Activity implements OnClickListener {
689dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel    private final static int MIN_NUMBER_OF_SURFACES = 15;
699dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel    private final static int MAX_NUMBER_OF_SURFACES = 40;
709dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel    private final static int WARM_UP_ALLOCATION_CYCLES = 2;
719dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel    private final static int MEASURE_ALLOCATION_CYCLES = 5;
729dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel    private final static int TEST_COMPOSITOR = 1;
739dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel    private final static int TEST_ALLOCATION = 2;
749dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel    private final static float MIN_REFRESH_RATE_SUPPORTED = 50.0f;
759dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel
769dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel    private final static DecimalFormat DOUBLE_FORMAT = new DecimalFormat("#.00");
779dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel    // Possible selection in pixel format selector.
789dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel    private final static int[] PIXEL_FORMATS = new int[] {
799dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel            PixelFormat.TRANSLUCENT,
809dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel            PixelFormat.TRANSPARENT,
819dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel            PixelFormat.OPAQUE,
829dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel            PixelFormat.RGBA_8888,
839dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel            PixelFormat.RGBX_8888,
849dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel            PixelFormat.RGB_888,
859dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel            PixelFormat.RGB_565,
869dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel    };
879dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel
889dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel
899dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel    private List<CustomSurfaceView> mViews = new ArrayList<CustomSurfaceView>();
909dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel    private Button mMeasureCompositionButton;
919dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel    private Button mMeasureAllocationButton;
929dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel    private Spinner mPixelFormatSelector;
939dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel    private TextView mResultView;
949dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel    private TextView mSystemInfoView;
959dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel    private final Object mLockResumed = new Object();
969dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel    private boolean mResumed;
979dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel
989dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel    // Drop one frame per half second.
999dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel    private double mRefreshRate;
1009dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel    private double mTargetFPS;
1017578d10f6cb2203ba3575b82b4b67e3a5d448bd7Yury Khmel    private boolean mAndromeda;
1029dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel
1039dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel    private int mWidth;
1049dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel    private int mHeight;
1059dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel
1069dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel    class CompositorScore {
1079dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        double mSurfaces;
1089e433bba26f3520baeb9a341069a4ff34e460410Yury Khmel        double mBandwidth;
1099dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel
1109dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        @Override
1119dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        public String toString() {
1129dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel            return DOUBLE_FORMAT.format(mSurfaces) + " surfaces. " +
1139e433bba26f3520baeb9a341069a4ff34e460410Yury Khmel                    "Bandwidth: " + getReadableMemory((long)mBandwidth) + "/s";
1149dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        }
1159dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel    }
1169dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel
1179dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel    /**
1189dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel     * Measure performance score.
1199dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel     *
1209dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel     * @return biggest possible number of visible surfaces which surface
1219dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel     *         compositor can handle.
1229dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel     */
1239dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel    public CompositorScore measureCompositionScore(int pixelFormat) {
1249dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        waitForActivityResumed();
1259dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        //MemoryAccessTask memAccessTask = new MemoryAccessTask();
1269dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        //memAccessTask.start();
1279dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        // Destroy any active surface.
1289dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        configureSurfacesAndWait(0, pixelFormat, false);
1299dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        CompositorScore score = new CompositorScore();
1309dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        score.mSurfaces = measureCompositionScore(new Measurement(0, 60.0),
1319dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel                new Measurement(mViews.size() + 1, 0.0f), pixelFormat);
1329dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        // Assume 32 bits per pixel.
1339e433bba26f3520baeb9a341069a4ff34e460410Yury Khmel        score.mBandwidth = score.mSurfaces * mTargetFPS * mWidth * mHeight * 4.0;
1349dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        //memAccessTask.stop();
1359dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        return score;
1369dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel    }
1379dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel
1389dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel    static class AllocationScore {
1399dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        double mMedian;
1409dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        double mMin;
1419dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        double mMax;
1429dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel
1439dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        @Override
1449dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        public String toString() {
1459dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel            return DOUBLE_FORMAT.format(mMedian) + " (min:" + DOUBLE_FORMAT.format(mMin) +
1469dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel                    ", max:" + DOUBLE_FORMAT.format(mMax) + ") surface allocations per second";
1479dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        }
1489dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel    }
1499dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel
1509dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel    public AllocationScore measureAllocationScore(int pixelFormat) {
1519dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        waitForActivityResumed();
1529dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        AllocationScore score = new AllocationScore();
1539dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        for (int i = 0; i < MEASURE_ALLOCATION_CYCLES + WARM_UP_ALLOCATION_CYCLES; ++i) {
1549dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel            long time1 = System.currentTimeMillis();
1559dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel            configureSurfacesAndWait(MIN_NUMBER_OF_SURFACES, pixelFormat, false);
1569dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel            acquireSurfacesCanvas();
1579dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel            long time2 = System.currentTimeMillis();
1589dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel            releaseSurfacesCanvas();
1599dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel            configureSurfacesAndWait(0, pixelFormat, false);
1609dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel            // Give SurfaceFlinger some time to rebuild the layer stack and release the buffers.
1619dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel            try {
1629dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel                Thread.sleep(500);
1639dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel            } catch(InterruptedException e) {
1649dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel                e.printStackTrace();
1659dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel            }
1669dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel            if (i < WARM_UP_ALLOCATION_CYCLES) {
1679dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel                // This is warm-up cycles, ignore result so far.
1689dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel                continue;
1699dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel            }
1709dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel            double speed = MIN_NUMBER_OF_SURFACES * 1000.0 / (time2 - time1);
1719dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel            score.mMedian += speed / MEASURE_ALLOCATION_CYCLES;
1729dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel            if (i == WARM_UP_ALLOCATION_CYCLES) {
1739dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel                score.mMin = speed;
1749dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel                score.mMax = speed;
1759dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel            } else {
1769dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel                score.mMin = Math.min(score.mMin, speed);
1779dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel                score.mMax = Math.max(score.mMax, speed);
1789dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel            }
1799dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        }
1809dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel
1819dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        return score;
1829dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel    }
1839dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel
1847578d10f6cb2203ba3575b82b4b67e3a5d448bd7Yury Khmel    public boolean isAndromeda() {
1857578d10f6cb2203ba3575b82b4b67e3a5d448bd7Yury Khmel        return mAndromeda;
1867578d10f6cb2203ba3575b82b4b67e3a5d448bd7Yury Khmel    }
1877578d10f6cb2203ba3575b82b4b67e3a5d448bd7Yury Khmel
1889dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel    @Override
1899dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel    public void onClick(View view) {
1909dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        if (view == mMeasureCompositionButton) {
1919dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel            doTest(TEST_COMPOSITOR);
1929dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        } else if (view == mMeasureAllocationButton) {
1939dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel            doTest(TEST_ALLOCATION);
1949dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        }
1959dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel    }
1969dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel
1979dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel    private void doTest(final int test) {
1989dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        enableControls(false);
1999dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        final int pixelFormat = PIXEL_FORMATS[mPixelFormatSelector.getSelectedItemPosition()];
2009dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        new Thread() {
2019dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel            public void run() {
2029dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel                final StringBuffer sb = new StringBuffer();
2039dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel                switch (test) {
2049dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel                    case TEST_COMPOSITOR: {
2059dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel                            sb.append("Compositor score:");
2069dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel                            CompositorScore score = measureCompositionScore(pixelFormat);
2079dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel                            sb.append("\n    " + getPixelFormatInfo(pixelFormat) + ":" +
2089dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel                                    score + ".");
2099dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel                        }
2109dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel                        break;
2119dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel                    case TEST_ALLOCATION: {
2129dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel                            sb.append("Allocation score:");
2139dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel                            AllocationScore score = measureAllocationScore(pixelFormat);
2149dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel                            sb.append("\n    " + getPixelFormatInfo(pixelFormat) + ":" +
2159dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel                                    score + ".");
2169dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel                        }
2179dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel                        break;
2189dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel                }
2199dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel                runOnUiThreadAndWait(new Runnable() {
2209dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel                    public void run() {
2219dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel                        mResultView.setText(sb.toString());
2229dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel                        enableControls(true);
2239dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel                        updateSystemInfo(pixelFormat);
2249dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel                    }
2259dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel                });
2269dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel            }
2279dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        }.start();
2289dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel    }
2299dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel
2309dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel    /**
2319dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel     * Wait until activity is resumed.
2329dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel     */
2339dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel    public void waitForActivityResumed() {
2349dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        synchronized (mLockResumed) {
2359dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel            if (!mResumed) {
2369dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel                try {
2379dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel                    mLockResumed.wait(10000);
2389dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel                } catch (InterruptedException e) {
2399dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel                }
2409dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel            }
2419dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel            if (!mResumed) {
2429dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel                throw new RuntimeException("Activity was not resumed");
2439dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel            }
2449dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        }
2459dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel    }
2469dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel
2479dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel    @Override
2489dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel    protected void onCreate(Bundle savedInstanceState) {
2499dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        super.onCreate(savedInstanceState);
2509dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel
2519dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
2529dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel
2537578d10f6cb2203ba3575b82b4b67e3a5d448bd7Yury Khmel        // Detect Andromeda devices by having free-form window management feature.
2547578d10f6cb2203ba3575b82b4b67e3a5d448bd7Yury Khmel        mAndromeda = getPackageManager().hasSystemFeature(
2557578d10f6cb2203ba3575b82b4b67e3a5d448bd7Yury Khmel                PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT);
2569dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        detectRefreshRate();
2579dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel
2589dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        // To layouts in parent. First contains list of Surfaces and second
2599dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        // controls. Controls stay on top.
2609dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        RelativeLayout rootLayout = new RelativeLayout(this);
2619dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        rootLayout.setLayoutParams(new ViewGroup.LayoutParams(
2629dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel                ViewGroup.LayoutParams.MATCH_PARENT,
2639dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel                ViewGroup.LayoutParams.MATCH_PARENT));
2649dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel
2659dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        CustomLayout layout = new CustomLayout(this);
2669dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        layout.setLayoutParams(new ViewGroup.LayoutParams(
2679dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel                ViewGroup.LayoutParams.MATCH_PARENT,
2689dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel                ViewGroup.LayoutParams.MATCH_PARENT));
2699dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel
2709dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        Rect rect = new Rect();
2719dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        getWindow().getDecorView().getWindowVisibleDisplayFrame(rect);
2729dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        mWidth = rect.right;
2739dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        mHeight = rect.bottom;
2749dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        long maxMemoryPerSurface = roundToNextPowerOf2(mWidth) * roundToNextPowerOf2(mHeight) * 4;
2759dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        // Use 75% of available memory.
2769dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        int surfaceCnt = (int)((getMemoryInfo().availMem * 3) / (4 * maxMemoryPerSurface));
2779dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        if (surfaceCnt < MIN_NUMBER_OF_SURFACES) {
2789dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel            throw new RuntimeException("Not enough memory to allocate " +
2799dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel                    MIN_NUMBER_OF_SURFACES + " surfaces.");
2809dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        }
2819dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        if (surfaceCnt > MAX_NUMBER_OF_SURFACES) {
2829dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel            surfaceCnt = MAX_NUMBER_OF_SURFACES;
2839dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        }
2849dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel
2859dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        LinearLayout controlLayout = new LinearLayout(this);
2869dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        controlLayout.setOrientation(LinearLayout.VERTICAL);
2879dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        controlLayout.setLayoutParams(new ViewGroup.LayoutParams(
2889dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel                ViewGroup.LayoutParams.MATCH_PARENT,
2899dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel                ViewGroup.LayoutParams.MATCH_PARENT));
2909dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel
2919dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        mMeasureCompositionButton = createButton("Compositor speed.", controlLayout);
2929dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        mMeasureAllocationButton = createButton("Allocation speed", controlLayout);
2939dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel
2949dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        String[] pixelFomats = new String[PIXEL_FORMATS.length];
2959dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        for (int i = 0; i < pixelFomats.length; ++i) {
2969dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel            pixelFomats[i] = getPixelFormatInfo(PIXEL_FORMATS[i]);
2979dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        }
2989dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        mPixelFormatSelector = new Spinner(this);
2999dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        ArrayAdapter<String> pixelFormatSelectorAdapter =
3009dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel                new ArrayAdapter<String>(this, android.R.layout.simple_spinner_item, pixelFomats);
3019dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        pixelFormatSelectorAdapter.setDropDownViewResource(
3029dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel                android.R.layout.simple_spinner_dropdown_item);
3039dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        mPixelFormatSelector.setAdapter(pixelFormatSelectorAdapter);
3049dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        mPixelFormatSelector.setLayoutParams(new LinearLayout.LayoutParams(
3059dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel                ViewGroup.LayoutParams.WRAP_CONTENT,
3069dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel                ViewGroup.LayoutParams.WRAP_CONTENT));
3079dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        controlLayout.addView(mPixelFormatSelector);
3089dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel
3099dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        mResultView = new TextView(this);
3109dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        mResultView.setBackgroundColor(0);
3119dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        mResultView.setText("Press button to start test.");
3129dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        mResultView.setLayoutParams(new LinearLayout.LayoutParams(
3139dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel                ViewGroup.LayoutParams.WRAP_CONTENT,
3149dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel                ViewGroup.LayoutParams.WRAP_CONTENT));
3159dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        controlLayout.addView(mResultView);
3169dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel
3179dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        mSystemInfoView = new TextView(this);
3189dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        mSystemInfoView.setBackgroundColor(0);
3199dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        mSystemInfoView.setLayoutParams(new LinearLayout.LayoutParams(
3209dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel                ViewGroup.LayoutParams.WRAP_CONTENT,
3219dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel                ViewGroup.LayoutParams.WRAP_CONTENT));
3229dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        controlLayout.addView(mSystemInfoView);
3239dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel
3249dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        for (int i = 0; i < surfaceCnt; ++i) {
3259dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel            CustomSurfaceView view = new CustomSurfaceView(this, "Surface:" + i);
3269dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel            // Create all surfaces overlapped in order to prevent SurfaceFlinger
3279dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel            // to filter out surfaces by optimization in case surface is opaque.
3289dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel            // In case surface is transparent it will be drawn anyway. Note that first
3299dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel            // surface covers whole screen and must stand below other surfaces. Z order of
3309dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel            // layers is not predictable and there is only one way to force first
3319dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel            // layer to be below others is to mark it as media and all other layers
3329dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel            // to mark as media overlay.
3339dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel            if (i == 0) {
3349dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel                view.setLayoutParams(new CustomLayout.LayoutParams(0, 0, mWidth, mHeight));
3359dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel                view.setZOrderMediaOverlay(false);
3369dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel            } else {
3379dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel                // Z order of other layers is not predefined so make offset on x and reverse
3389dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel                // offset on y to make sure that surface is visible in any layout.
3399dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel                int x = i;
3409dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel                int y = (surfaceCnt - i);
3419dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel                view.setLayoutParams(new CustomLayout.LayoutParams(x, y, x + mWidth, y + mHeight));
3429dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel                view.setZOrderMediaOverlay(true);
3439dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel            }
3449dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel            view.setVisibility(View.INVISIBLE);
3459dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel            layout.addView(view);
3469dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel            mViews.add(view);
3479dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        }
3489dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel
3499dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        rootLayout.addView(layout);
3509dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        rootLayout.addView(controlLayout);
3519dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel
3529dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        setContentView(rootLayout);
3539dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel    }
3549dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel
3559dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel    private Button createButton(String caption, LinearLayout layout) {
3569dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        Button button = new Button(this);
3579dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        button.setText(caption);
3589dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        button.setLayoutParams(new LinearLayout.LayoutParams(
3599dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel                ViewGroup.LayoutParams.WRAP_CONTENT,
3609dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel                ViewGroup.LayoutParams.WRAP_CONTENT));
3619dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        button.setOnClickListener(this);
3629dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        layout.addView(button);
3639dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        return button;
3649dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel    }
3659dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel
3669dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel    private void enableControls(boolean enabled) {
3679dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        mMeasureCompositionButton.setEnabled(enabled);
3689dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        mMeasureAllocationButton.setEnabled(enabled);
3699dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        mPixelFormatSelector.setEnabled(enabled);
3709dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel    }
3719dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel
3729dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel    @Override
3739dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel    protected void onResume() {
3749dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        super.onResume();
3759dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel
3769dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        updateSystemInfo(PixelFormat.UNKNOWN);
3779dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel
3789dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        synchronized (mLockResumed) {
3799dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel            mResumed = true;
3809dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel            mLockResumed.notifyAll();
3819dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        }
3829dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel    }
3839dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel
3849dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel    @Override
3859dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel    protected void onPause() {
3869dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        super.onPause();
3879dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel
3889dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        synchronized (mLockResumed) {
3899dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel            mResumed = false;
3909dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        }
3919dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel    }
3929dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel
3939dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel    class Measurement {
3949dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        Measurement(int surfaceCnt, double fps) {
3959dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel            mSurfaceCnt = surfaceCnt;
3969dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel            mFPS = fps;
3979dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        }
3989dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel
3999dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        public final int mSurfaceCnt;
4009dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        public final double mFPS;
4019dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel    }
4029dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel
4039dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel    private double measureCompositionScore(Measurement ok, Measurement fail, int pixelFormat) {
4049dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        if (ok.mSurfaceCnt + 1 == fail.mSurfaceCnt) {
4059dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel            // Interpolate result.
4069dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel            double fraction = (mTargetFPS - fail.mFPS) / (ok.mFPS - fail.mFPS);
4079dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel            return ok.mSurfaceCnt + fraction;
4089dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        }
4099dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel
4109dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        int medianSurfaceCnt = (ok.mSurfaceCnt + fail.mSurfaceCnt) / 2;
4119dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        Measurement median = new Measurement(medianSurfaceCnt,
4129dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel                measureFPS(medianSurfaceCnt, pixelFormat));
4139dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel
4149dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        if (median.mFPS >= mTargetFPS) {
4159dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel            return measureCompositionScore(median, fail, pixelFormat);
4169dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        } else {
4179dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel            return measureCompositionScore(ok, median, pixelFormat);
4189dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        }
4199dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel    }
4209dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel
4219dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel    private double measureFPS(int surfaceCnt, int pixelFormat) {
4229dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        configureSurfacesAndWait(surfaceCnt, pixelFormat, true);
4239dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        // At least one view is visible and it is enough to update only
4249dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        // one overlapped surface in order to force SurfaceFlinger to send
4259dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        // all surfaces to compositor.
4269dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        double fps = mViews.get(0).measureFPS(mRefreshRate * 0.8, mRefreshRate * 0.999);
4279dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel
4289dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        // Make sure that surface configuration was not changed.
4299dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        validateSurfacesNotChanged();
4309dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel
4319dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        return fps;
4329dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel    }
4339dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel
4349dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel    private void waitForSurfacesConfigured(final int pixelFormat) {
4359dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        for (int i = 0; i < mViews.size(); ++i) {
4369dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel            CustomSurfaceView view = mViews.get(i);
4379dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel            if (view.getVisibility() == View.VISIBLE) {
4389dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel                view.waitForSurfaceReady();
4399dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel            } else {
4409dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel                view.waitForSurfaceDestroyed();
4419dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel            }
4429dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        }
4439dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        runOnUiThreadAndWait(new Runnable() {
4449dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel            @Override
4459dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel            public void run() {
4469dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel                updateSystemInfo(pixelFormat);
4479dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel            }
4489dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        });
4499dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel    }
4509dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel
4519dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel    private void validateSurfacesNotChanged() {
4529dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        for (int i = 0; i < mViews.size(); ++i) {
4539dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel            CustomSurfaceView view = mViews.get(i);
4549dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel            view.validateSurfaceNotChanged();
4559dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        }
4569dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel    }
4579dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel
4589dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel    private void configureSurfaces(int surfaceCnt, int pixelFormat, boolean invalidate) {
4599dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        for (int i = 0; i < mViews.size(); ++i) {
4609dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel            CustomSurfaceView view = mViews.get(i);
4619dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel            if (i < surfaceCnt) {
4629dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel                view.setMode(pixelFormat, invalidate);
4639dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel                view.setVisibility(View.VISIBLE);
4649dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel            } else {
4659dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel                view.setVisibility(View.INVISIBLE);
4669dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel            }
4679dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        }
4689dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel    }
4699dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel
4709dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel    private void configureSurfacesAndWait(final int surfaceCnt, final int pixelFormat,
4719dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel            final boolean invalidate) {
4729dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        runOnUiThreadAndWait(new Runnable() {
4739dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel            @Override
4749dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel            public void run() {
4759dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel                configureSurfaces(surfaceCnt, pixelFormat, invalidate);
4769dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel            }
4779dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        });
4789dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        waitForSurfacesConfigured(pixelFormat);
4799dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel    }
4809dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel
4819dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel    private void acquireSurfacesCanvas() {
4829dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        for (int i = 0; i < mViews.size(); ++i) {
4839dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel            CustomSurfaceView view = mViews.get(i);
4849dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel            view.acquireCanvas();
4859dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        }
4869dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel    }
4879dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel
4889dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel    private void releaseSurfacesCanvas() {
4899dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        for (int i = 0; i < mViews.size(); ++i) {
4909dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel            CustomSurfaceView view = mViews.get(i);
4919dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel            view.releaseCanvas();
4929dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        }
4939dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel    }
4949dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel
4959dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel    private static String getReadableMemory(long bytes) {
4969dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        long unit = 1024;
4979dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        if (bytes < unit) {
4989dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel            return bytes + " B";
4999dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        }
5009dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        int exp = (int) (Math.log(bytes) / Math.log(unit));
5019dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        return String.format("%.1f %sB", bytes / Math.pow(unit, exp),
5029dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel                "KMGTPE".charAt(exp-1));
5039dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel    }
5049dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel
5059dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel    private MemoryInfo getMemoryInfo() {
5069dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        ActivityManager activityManager = (ActivityManager)
5079dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel                getSystemService(ACTIVITY_SERVICE);
5089dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        MemoryInfo memInfo = new MemoryInfo();
5099dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        activityManager.getMemoryInfo(memInfo);
5109dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        return memInfo;
5119dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel    }
5129dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel
5139dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel    private void updateSystemInfo(int pixelFormat) {
5149dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        int visibleCnt = 0;
5159dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        for (int i = 0; i < mViews.size(); ++i) {
5169dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel            if (mViews.get(i).getVisibility() == View.VISIBLE) {
5179dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel                ++visibleCnt;
5189dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel            }
5199dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        }
5209dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel
5219dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        MemoryInfo memInfo = getMemoryInfo();
5227578d10f6cb2203ba3575b82b4b67e3a5d448bd7Yury Khmel        String platformName = mAndromeda ? "Andromeda" : "Android";
5237578d10f6cb2203ba3575b82b4b67e3a5d448bd7Yury Khmel        String info = platformName + ": available " +
5249dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel                getReadableMemory(memInfo.availMem) + " from " +
5259dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel                getReadableMemory(memInfo.totalMem) + ".\nVisible " +
5269dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel                visibleCnt + " from " + mViews.size() + " " +
5279dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel                getPixelFormatInfo(pixelFormat) + " surfaces.\n" +
5289dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel                "View size: " + mWidth + "x" + mHeight +
5299dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel                ". Refresh rate: " + DOUBLE_FORMAT.format(mRefreshRate) + ".";
5309dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        mSystemInfoView.setText(info);
5319dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel    }
5329dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel
5339dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel    private void detectRefreshRate() {
5349dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        WindowManager wm = (WindowManager)getSystemService(Context.WINDOW_SERVICE);
5359dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        mRefreshRate = wm.getDefaultDisplay().getRefreshRate();
5369dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        if (mRefreshRate < MIN_REFRESH_RATE_SUPPORTED)
5379dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel            throw new RuntimeException("Unsupported display refresh rate: " + mRefreshRate);
5389dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        mTargetFPS = mRefreshRate - 2.0f;
5399dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel    }
5409dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel
5419dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel    private int roundToNextPowerOf2(int value) {
5429dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        --value;
5439dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        value |= value >> 1;
5449dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        value |= value >> 2;
5459dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        value |= value >> 4;
5469dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        value |= value >> 8;
5479dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        value |= value >> 16;
5489dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        return value + 1;
5499dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel    }
5509dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel
5519dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel    public static String getPixelFormatInfo(int pixelFormat) {
5529dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        switch (pixelFormat) {
5539dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        case PixelFormat.TRANSLUCENT:
5549dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel            return "TRANSLUCENT";
5559dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        case PixelFormat.TRANSPARENT:
5569dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel            return "TRANSPARENT";
5579dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        case PixelFormat.OPAQUE:
5589dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel            return "OPAQUE";
5599dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        case PixelFormat.RGBA_8888:
5609dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel            return "RGBA_8888";
5619dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        case PixelFormat.RGBX_8888:
5629dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel            return "RGBX_8888";
5639dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        case PixelFormat.RGB_888:
5649dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel            return "RGB_888";
5659dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        case PixelFormat.RGB_565:
5669dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel            return "RGB_565";
5679dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        default:
5689dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel            return "PIX.FORMAT:" + pixelFormat;
5699dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        }
5709dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel    }
5719dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel
5729dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel    /**
5739dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel     * A helper that executes a task in the UI thread and waits for its completion.
5749dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel     *
5759dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel     * @param task - task to execute.
5769dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel     */
5779dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel    private void runOnUiThreadAndWait(Runnable task) {
5789dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        new UIExecutor(task);
5799dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel    }
5809dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel
5819dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel    class UIExecutor implements Runnable {
5829dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        private final Object mLock = new Object();
5839dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        private Runnable mTask;
5849dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        private boolean mDone = false;
5859dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel
5869dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        UIExecutor(Runnable task) {
5879dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel            mTask = task;
5889dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel            mDone = false;
5899dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel            runOnUiThread(this);
5909dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel            synchronized (mLock) {
5919dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel                while (!mDone) {
5929dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel                    try {
5939dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel                        mLock.wait();
5949dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel                    } catch (InterruptedException e) {
5959dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel                        e.printStackTrace();
5969dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel                    }
5979dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel                }
5989dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel            }
5999dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        }
6009dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel
6019dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        public void run() {
6029dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel            mTask.run();
6039dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel            synchronized (mLock) {
6049dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel                mDone = true;
6059dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel                mLock.notify();
6069dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel            }
6079dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel        }
6089dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel    }
6099dbde7b09f2366d2a239b1a4c234d5cf2de51739Yury Khmel}
610