Merge changes from topic "jank-toggle" into tm-dev
* changes: Add SettingsJankMonitor Add SETTINGS_TOGGLE CUJ to InteractionJankMonitor
This commit is contained in:
@@ -20,6 +20,7 @@ import static com.android.internal.jank.FrameTracker.REASON_CANCEL_NORMAL;
|
||||
import static com.android.internal.jank.FrameTracker.REASON_CANCEL_TIMEOUT;
|
||||
import static com.android.internal.jank.FrameTracker.REASON_END_NORMAL;
|
||||
import static com.android.internal.jank.FrameTracker.REASON_END_UNKNOWN;
|
||||
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__BIOMETRIC_PROMPT_TRANSITION;
|
||||
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_ALL_APPS_SCROLL;
|
||||
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_CLOSE_TO_HOME;
|
||||
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_CLOSE_TO_PIP;
|
||||
@@ -46,6 +47,7 @@ import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_IN
|
||||
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SCREEN_OFF_SHOW_AOD;
|
||||
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SETTINGS_PAGE_SCROLL;
|
||||
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SETTINGS_SLIDER;
|
||||
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SETTINGS_TOGGLE;
|
||||
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH;
|
||||
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH_FROM_HISTORY_BUTTON;
|
||||
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH_FROM_MEDIA_PLAYER;
|
||||
@@ -198,6 +200,7 @@ public class InteractionJankMonitor {
|
||||
public static final int CUJ_SETTINGS_SLIDER = 53;
|
||||
public static final int CUJ_TAKE_SCREENSHOT = 54;
|
||||
public static final int CUJ_VOLUME_CONTROL = 55;
|
||||
public static final int CUJ_SETTINGS_TOGGLE = 57;
|
||||
|
||||
private static final int NO_STATSD_LOGGING = -1;
|
||||
|
||||
@@ -262,6 +265,8 @@ public class InteractionJankMonitor {
|
||||
UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SETTINGS_SLIDER,
|
||||
UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__TAKE_SCREENSHOT,
|
||||
UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__VOLUME_CONTROL,
|
||||
UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__BIOMETRIC_PROMPT_TRANSITION,
|
||||
UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SETTINGS_TOGGLE,
|
||||
};
|
||||
|
||||
private static volatile InteractionJankMonitor sInstance;
|
||||
@@ -338,6 +343,7 @@ public class InteractionJankMonitor {
|
||||
CUJ_SETTINGS_SLIDER,
|
||||
CUJ_TAKE_SCREENSHOT,
|
||||
CUJ_VOLUME_CONTROL,
|
||||
CUJ_SETTINGS_TOGGLE,
|
||||
})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface CujType {
|
||||
@@ -768,6 +774,8 @@ public class InteractionJankMonitor {
|
||||
return "TAKE_SCREENSHOT";
|
||||
case CUJ_VOLUME_CONTROL:
|
||||
return "VOLUME_CONTROL";
|
||||
case CUJ_SETTINGS_TOGGLE:
|
||||
return "SETTINGS_TOGGLE";
|
||||
}
|
||||
return "UNKNOWN";
|
||||
}
|
||||
|
||||
@@ -19,8 +19,6 @@ package com.android.settingslib;
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.widget.Switch;
|
||||
|
||||
import androidx.annotation.Keep;
|
||||
@@ -28,6 +26,7 @@ import androidx.annotation.Nullable;
|
||||
import androidx.preference.PreferenceViewHolder;
|
||||
|
||||
import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
|
||||
import com.android.settingslib.core.instrumentation.SettingsJankMonitor;
|
||||
|
||||
/**
|
||||
* A custom preference that provides inline switch toggle. It has a mandatory field for title, and
|
||||
@@ -65,31 +64,25 @@ public class PrimarySwitchPreference extends RestrictedPreference {
|
||||
@Override
|
||||
public void onBindViewHolder(PreferenceViewHolder holder) {
|
||||
super.onBindViewHolder(holder);
|
||||
final View switchWidget = holder.findViewById(R.id.switchWidget);
|
||||
if (switchWidget != null) {
|
||||
switchWidget.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (mSwitch != null && !mSwitch.isEnabled()) {
|
||||
return;
|
||||
}
|
||||
setChecked(!mChecked);
|
||||
if (!callChangeListener(mChecked)) {
|
||||
setChecked(!mChecked);
|
||||
} else {
|
||||
persistBoolean(mChecked);
|
||||
}
|
||||
mSwitch = (Switch) holder.findViewById(R.id.switchWidget);
|
||||
if (mSwitch != null) {
|
||||
mSwitch.setOnClickListener(v -> {
|
||||
if (mSwitch != null && !mSwitch.isEnabled()) {
|
||||
return;
|
||||
}
|
||||
final boolean newChecked = !mChecked;
|
||||
if (callChangeListener(newChecked)) {
|
||||
SettingsJankMonitor.detectToggleJank(getKey(), mSwitch);
|
||||
setChecked(newChecked);
|
||||
persistBoolean(newChecked);
|
||||
}
|
||||
});
|
||||
|
||||
// Consumes move events to ignore drag actions.
|
||||
switchWidget.setOnTouchListener((v, event) -> {
|
||||
mSwitch.setOnTouchListener((v, event) -> {
|
||||
return event.getActionMasked() == MotionEvent.ACTION_MOVE;
|
||||
});
|
||||
}
|
||||
|
||||
mSwitch = (Switch) holder.findViewById(R.id.switchWidget);
|
||||
if (mSwitch != null) {
|
||||
mSwitch.setContentDescription(getTitle());
|
||||
mSwitch.setChecked(mChecked);
|
||||
mSwitch.setEnabled(mEnableSwitch);
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* Copyright (C) 2022 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.settingslib.core.instrumentation
|
||||
|
||||
import android.view.View
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import androidx.preference.PreferenceGroupAdapter
|
||||
import androidx.preference.SwitchPreference
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.android.internal.jank.InteractionJankMonitor
|
||||
import java.util.concurrent.Executors
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
/**
|
||||
* Helper class for Settings library to trace jank.
|
||||
*/
|
||||
object SettingsJankMonitor {
|
||||
private val jankMonitor = InteractionJankMonitor.getInstance()
|
||||
private val scheduledExecutorService = Executors.newSingleThreadScheduledExecutor()
|
||||
|
||||
// Switch toggle animation duration is 250ms, and there is also a ripple effect animation when
|
||||
// clicks, which duration is variable. Use 300ms here to cover.
|
||||
@VisibleForTesting
|
||||
const val MONITORED_ANIMATION_DURATION_MS = 300L
|
||||
|
||||
/**
|
||||
* Detects the jank when click on a SwitchPreference.
|
||||
*
|
||||
* @param recyclerView the recyclerView contains the preference
|
||||
* @param preference the clicked preference
|
||||
*/
|
||||
@JvmStatic
|
||||
fun detectSwitchPreferenceClickJank(recyclerView: RecyclerView, preference: SwitchPreference) {
|
||||
val adapter = recyclerView.adapter as? PreferenceGroupAdapter ?: return
|
||||
val adapterPosition = adapter.getPreferenceAdapterPosition(preference)
|
||||
val viewHolder = recyclerView.findViewHolderForAdapterPosition(adapterPosition) ?: return
|
||||
detectToggleJank(preference.key, viewHolder.itemView)
|
||||
}
|
||||
|
||||
/**
|
||||
* Detects the animation jank on the given view.
|
||||
*
|
||||
* @param tag the tag for jank monitor
|
||||
* @param view the instrumented view
|
||||
*/
|
||||
@JvmStatic
|
||||
fun detectToggleJank(tag: String?, view: View) {
|
||||
val builder = InteractionJankMonitor.Configuration.Builder.withView(
|
||||
InteractionJankMonitor.CUJ_SETTINGS_TOGGLE,
|
||||
view
|
||||
)
|
||||
if (tag != null) {
|
||||
builder.setTag(tag)
|
||||
}
|
||||
if (jankMonitor.begin(builder)) {
|
||||
scheduledExecutorService.schedule({
|
||||
jankMonitor.end(InteractionJankMonitor.CUJ_SETTINGS_TOGGLE)
|
||||
}, MONITORED_ANIMATION_DURATION_MS, TimeUnit.MILLISECONDS)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -63,6 +63,7 @@ java_library {
|
||||
|
||||
libs: [
|
||||
"Robolectric_all-target",
|
||||
"mockito-robolectric-prebuilt",
|
||||
"truth-prebuilt",
|
||||
],
|
||||
}
|
||||
|
||||
@@ -30,14 +30,17 @@ import androidx.preference.Preference.OnPreferenceChangeListener;
|
||||
import androidx.preference.PreferenceViewHolder;
|
||||
|
||||
import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
|
||||
import com.android.settingslib.testutils.shadow.ShadowInteractionJankMonitor;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.RuntimeEnvironment;
|
||||
import org.robolectric.annotation.Config;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
@Config(shadows = {ShadowInteractionJankMonitor.class})
|
||||
public class PrimarySwitchPreferenceTest {
|
||||
|
||||
private Context mContext;
|
||||
|
||||
@@ -0,0 +1,144 @@
|
||||
/*
|
||||
* Copyright (C) 2022 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.settingslib.core.instrumentation;
|
||||
|
||||
import static com.android.internal.jank.InteractionJankMonitor.CUJ_SETTINGS_TOGGLE;
|
||||
import static com.android.settingslib.core.instrumentation.SettingsJankMonitor.MONITORED_ANIMATION_DURATION_MS;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.preference.PreferenceGroupAdapter;
|
||||
import androidx.preference.SwitchPreference;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.android.internal.jank.InteractionJankMonitor;
|
||||
import com.android.internal.jank.InteractionJankMonitor.CujType;
|
||||
import com.android.settingslib.testutils.shadow.ShadowInteractionJankMonitor;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnit;
|
||||
import org.mockito.junit.MockitoRule;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.annotation.Config;
|
||||
import org.robolectric.annotation.Implementation;
|
||||
import org.robolectric.annotation.Implements;
|
||||
import org.robolectric.annotation.Resetter;
|
||||
import org.robolectric.util.ReflectionHelpers;
|
||||
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
@Config(shadows = {ShadowInteractionJankMonitor.class, SettingsJankMonitorTest.ShadowBuilder.class})
|
||||
public class SettingsJankMonitorTest {
|
||||
private static final String TEST_KEY = "key";
|
||||
|
||||
@Rule
|
||||
public MockitoRule mocks = MockitoJUnit.rule();
|
||||
|
||||
@Mock
|
||||
private View mView;
|
||||
|
||||
@Mock
|
||||
private RecyclerView mRecyclerView;
|
||||
|
||||
@Mock
|
||||
private PreferenceGroupAdapter mPreferenceGroupAdapter;
|
||||
|
||||
@Mock
|
||||
private SwitchPreference mSwitchPreference;
|
||||
|
||||
@Mock
|
||||
private ScheduledExecutorService mScheduledExecutorService;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
ShadowInteractionJankMonitor.reset();
|
||||
when(ShadowInteractionJankMonitor.MOCK_INSTANCE.begin(any())).thenReturn(true);
|
||||
ReflectionHelpers.setStaticField(SettingsJankMonitor.class, "scheduledExecutorService",
|
||||
mScheduledExecutorService);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void detectToggleJank() {
|
||||
SettingsJankMonitor.detectToggleJank(TEST_KEY, mView);
|
||||
|
||||
verifyToggleJankMonitored();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void detectSwitchPreferenceClickJank() {
|
||||
int adapterPosition = 7;
|
||||
when(mRecyclerView.getAdapter()).thenReturn(mPreferenceGroupAdapter);
|
||||
when(mPreferenceGroupAdapter.getPreferenceAdapterPosition(mSwitchPreference))
|
||||
.thenReturn(adapterPosition);
|
||||
when(mRecyclerView.findViewHolderForAdapterPosition(adapterPosition))
|
||||
.thenReturn(new RecyclerView.ViewHolder(mView) {
|
||||
});
|
||||
when(mSwitchPreference.getKey()).thenReturn(TEST_KEY);
|
||||
|
||||
SettingsJankMonitor.detectSwitchPreferenceClickJank(mRecyclerView, mSwitchPreference);
|
||||
|
||||
verifyToggleJankMonitored();
|
||||
}
|
||||
|
||||
private void verifyToggleJankMonitored() {
|
||||
verify(ShadowInteractionJankMonitor.MOCK_INSTANCE).begin(ShadowBuilder.sBuilder);
|
||||
assertThat(ShadowBuilder.sView).isSameInstanceAs(mView);
|
||||
ArgumentCaptor<Runnable> runnableCaptor = ArgumentCaptor.forClass(Runnable.class);
|
||||
verify(mScheduledExecutorService).schedule(runnableCaptor.capture(),
|
||||
eq(MONITORED_ANIMATION_DURATION_MS), eq(TimeUnit.MILLISECONDS));
|
||||
runnableCaptor.getValue().run();
|
||||
verify(ShadowInteractionJankMonitor.MOCK_INSTANCE).end(CUJ_SETTINGS_TOGGLE);
|
||||
}
|
||||
|
||||
@Implements(InteractionJankMonitor.Configuration.Builder.class)
|
||||
static class ShadowBuilder {
|
||||
private static InteractionJankMonitor.Configuration.Builder sBuilder;
|
||||
private static View sView;
|
||||
|
||||
@Resetter
|
||||
public static void reset() {
|
||||
sBuilder = null;
|
||||
sView = null;
|
||||
}
|
||||
|
||||
@Implementation
|
||||
public static InteractionJankMonitor.Configuration.Builder withView(
|
||||
@CujType int cuj, @NonNull View view) {
|
||||
assertThat(cuj).isEqualTo(CUJ_SETTINGS_TOGGLE);
|
||||
sView = view;
|
||||
sBuilder = mock(InteractionJankMonitor.Configuration.Builder.class);
|
||||
when(sBuilder.setTag(TEST_KEY)).thenReturn(sBuilder);
|
||||
return sBuilder;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright (C) 2021 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.settingslib.testutils.shadow;
|
||||
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
import com.android.internal.jank.InteractionJankMonitor;
|
||||
|
||||
import org.mockito.Mockito;
|
||||
import org.robolectric.annotation.Implementation;
|
||||
import org.robolectric.annotation.Implements;
|
||||
import org.robolectric.annotation.Resetter;
|
||||
|
||||
@Implements(InteractionJankMonitor.class)
|
||||
public class ShadowInteractionJankMonitor {
|
||||
public static final InteractionJankMonitor MOCK_INSTANCE = mock(InteractionJankMonitor.class);
|
||||
|
||||
@Resetter
|
||||
public static void reset() {
|
||||
Mockito.reset(MOCK_INSTANCE);
|
||||
}
|
||||
|
||||
@Implementation
|
||||
public static InteractionJankMonitor getInstance() {
|
||||
return MOCK_INSTANCE;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user