1 /*
2  * Copyright (C) 2024 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.example.android.apis.view;
18 
19 import android.animation.Animator;
20 import android.animation.AnimatorSet;
21 import android.animation.ObjectAnimator;
22 import android.app.Activity;
23 import android.graphics.Rect;
24 import android.os.Bundle;
25 import android.view.View;
26 import android.widget.ProgressBar;
27 import android.widget.RadioButton;
28 import android.widget.SeekBar;
29 import android.widget.TextView;
30 
31 import com.example.android.apis.R;
32 
33 import java.util.ArrayList;
34 import java.util.Arrays;
35 
36 public class ViewFrameRateActivity extends Activity {
37     private float mFrameRate = View.REQUESTED_FRAME_RATE_CATEGORY_NO_PREFERENCE;
38     private long mLastFrameNanos = 0L;
39     private int mLastRenderRate = 0;
40     private final int[] mFrameRates = new int[] { 120, 90, 80, 60, 48, 30, 24, 15, 12, 8, 4, 2, 1 };
41     private final long[] mFrameRateCutoffs = new long[mFrameRates.length - 1];
42 
43     private ProgressBar mProgressBar;
44     private View mAnimatedView;
45     private Animator mAnimator;
46 
ViewFrameRateActivity()47     public ViewFrameRateActivity() {
48         for (int i = 0; i < mFrameRateCutoffs.length; i++) {
49             long low = 1_000_000_000L / mFrameRates[i];
50             long high = 1_000_000_000L / mFrameRates[i + 1];
51             mFrameRateCutoffs[i] = (high * 3 + low) / 4; // 3/4 of the way to high is the cut-off
52         }
53     }
54 
updateFrameRate(float newFrameRate)55     private void updateFrameRate(float newFrameRate) {
56         if (mFrameRate != newFrameRate) {
57             mFrameRate = newFrameRate;
58             mProgressBar.setRequestedFrameRate(newFrameRate);
59             mAnimatedView.setRequestedFrameRate(newFrameRate);
60         }
61     }
62 
63     @Override
onCreate(Bundle savedInstanceState)64     public void onCreate(Bundle savedInstanceState) {
65         super.onCreate(savedInstanceState);
66         setContentView(R.layout.view_frame_rate);
67         final SeekBar frameRateSeekBar = findViewById(R.id.frameRateSeekBar);
68         assert frameRateSeekBar != null;
69         frameRateSeekBar.setRequestedFrameRate(View.REQUESTED_FRAME_RATE_CATEGORY_NO_PREFERENCE);
70         frameRateSeekBar.addOnLayoutChangeListener((v, l, t, r, b, ol, ot, or, ob) -> {
71             Rect rect = new Rect(0, 0, v.getWidth(), v.getHeight());
72             ArrayList<Rect> list = new ArrayList<>();
73             list.add(rect);
74             frameRateSeekBar.setSystemGestureExclusionRects(list);
75         });
76         final TextView frameRateText = findViewById(R.id.frameRateText);
77         assert frameRateText != null;
78         frameRateText.setRequestedFrameRate(View.REQUESTED_FRAME_RATE_CATEGORY_NO_PREFERENCE);
79         mProgressBar = findViewById(R.id.progressBar);
80         assert mProgressBar != null;
81         mProgressBar.setRequestedFrameRate(View.REQUESTED_FRAME_RATE_CATEGORY_NO_PREFERENCE);
82         mAnimatedView = findViewById(R.id.animatedView);
83         assert mAnimatedView != null;
84         mAnimatedView.setRequestedFrameRate(View.REQUESTED_FRAME_RATE_CATEGORY_NO_PREFERENCE);
85 
86         final RadioButton noPreference = findViewById(R.id.noPreference);
87         assert noPreference != null;
88         noPreference.setRequestedFrameRate(View.REQUESTED_FRAME_RATE_CATEGORY_NO_PREFERENCE);
89         noPreference.setOnCheckedChangeListener((v, isChecked) -> {
90             if (isChecked) {
91                 frameRateText.setText("No Pref");
92                 updateFrameRate(View.REQUESTED_FRAME_RATE_CATEGORY_NO_PREFERENCE);
93             }
94         });
95         final RadioButton low = findViewById(R.id.low);
96         assert low != null;
97         low.setRequestedFrameRate(View.REQUESTED_FRAME_RATE_CATEGORY_NO_PREFERENCE);
98         low.setOnCheckedChangeListener((v, isChecked) -> {
99             if (isChecked) {
100                 frameRateText.setText("Low");
101                 updateFrameRate(View.REQUESTED_FRAME_RATE_CATEGORY_LOW);
102             }
103         });
104         final RadioButton normal = findViewById(R.id.normal);
105         assert normal != null;
106         normal.setRequestedFrameRate(View.REQUESTED_FRAME_RATE_CATEGORY_NO_PREFERENCE);
107         normal.setOnCheckedChangeListener((v, isChecked) -> {
108             if (isChecked) {
109                 frameRateText.setText("Normal");
110                 updateFrameRate(View.REQUESTED_FRAME_RATE_CATEGORY_NORMAL);
111             }
112         });
113         final RadioButton high = findViewById(R.id.high);
114         assert high != null;
115         high.setRequestedFrameRate(View.REQUESTED_FRAME_RATE_CATEGORY_NO_PREFERENCE);
116         high.setOnCheckedChangeListener((v, isChecked) -> {
117             if (isChecked) {
118                 frameRateText.setText("High");
119                 updateFrameRate(View.REQUESTED_FRAME_RATE_CATEGORY_HIGH);
120             }
121         });
122         frameRateSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
123             @Override
124             public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
125                 frameRateText.setText(String.valueOf(seekBar.getProgress()));
126                 updateFrameRate(progress);
127             }
128 
129             @Override
130             public void onStartTrackingTouch(SeekBar seekBar) {
131                 frameRateText.setText(String.valueOf(seekBar.getProgress()));
132                 noPreference.setChecked(false);
133                 low.setChecked(false);
134                 normal.setChecked(false);
135                 high.setChecked(false);
136             }
137 
138             @Override
139             public void onStopTrackingTouch(SeekBar seekBar) {
140             }
141         });
142         final TextView frameRateOutput = findViewById(R.id.frameRate);
143         assert frameRateOutput != null;
144         frameRateOutput.setRequestedFrameRate(View.REQUESTED_FRAME_RATE_CATEGORY_NO_PREFERENCE);
145 
146         final RadioButton vectorDrawable = findViewById(R.id.vectorDrawable);
147         assert vectorDrawable != null;
148         vectorDrawable.setOnCheckedChangeListener((v, isChecked) -> {
149             if (isChecked) {
150                 mProgressBar.setVisibility(View.VISIBLE);
151                 mAnimatedView.setVisibility(View.GONE);
152                 mAnimator.pause();
153                 frameRateOutput.setVisibility(View.GONE);
154             }
155         });
156         final RadioButton view = findViewById(R.id.view);
157         assert view != null;
158         view.setOnCheckedChangeListener((v, isChecked) -> {
159             if (isChecked) {
160                 mProgressBar.setVisibility(View.GONE);
161                 mAnimatedView.setVisibility(View.VISIBLE);
162                 mAnimator.resume();
163                 frameRateOutput.setVisibility(View.VISIBLE);
164             }
165         });
166         mProgressBar.getViewTreeObserver().addOnDrawListener(() -> {
167             long now = System.nanoTime();
168             long delta = now - mLastFrameNanos;
169             if (mLastFrameNanos > 0L && delta > 0L) {
170                 int searchIndex = Arrays.binarySearch(mFrameRateCutoffs, delta);
171                 int frameRateIndex = searchIndex >= 0
172                         ? searchIndex
173                         : Math.min(mFrameRates.length - 1, ~searchIndex);
174                 int newRenderRate = mFrameRates[frameRateIndex];
175                 if (newRenderRate != mLastRenderRate) {
176                     mLastRenderRate = newRenderRate;
177                     frameRateOutput.setText(String.valueOf(mLastRenderRate));
178                 }
179             }
180             mLastFrameNanos = now;
181         });
182         ObjectAnimator scaleX = ObjectAnimator.ofFloat(mAnimatedView, View.SCALE_X, 0.25f, 0.75f);
183         scaleX.setRepeatCount(ObjectAnimator.INFINITE);
184         scaleX.setRepeatMode(ObjectAnimator.REVERSE);
185         ObjectAnimator scaleY = ObjectAnimator.ofFloat(mAnimatedView, View.SCALE_Y, 0.25f, 0.75f);
186         scaleY.setRepeatCount(ObjectAnimator.INFINITE);
187         scaleY.setRepeatMode(ObjectAnimator.REVERSE);
188         AnimatorSet set = new AnimatorSet();
189         set.setDuration(1000);
190         set.playTogether(scaleX, scaleY);
191         mAnimator = set;
192         set.start();
193         set.pause();
194     }
195 }
196