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