1 /* 2 * Copyright (C) 2016 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.android.settings.core; 18 19 import static com.android.internal.jank.InteractionJankMonitor.CUJ_SETTINGS_PAGE_SCROLL; 20 import static com.android.internal.jank.InteractionJankMonitor.Configuration; 21 22 import android.content.Context; 23 import android.os.Bundle; 24 import android.text.TextUtils; 25 import android.util.Log; 26 27 import androidx.annotation.XmlRes; 28 import androidx.preference.Preference; 29 import androidx.preference.PreferenceScreen; 30 import androidx.preference.SwitchPreference; 31 import androidx.recyclerview.widget.RecyclerView; 32 33 import com.android.internal.jank.InteractionJankMonitor; 34 import com.android.settings.overlay.FeatureFactory; 35 import com.android.settings.survey.SurveyMixin; 36 import com.android.settingslib.core.instrumentation.Instrumentable; 37 import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; 38 import com.android.settingslib.core.instrumentation.SettingsJankMonitor; 39 import com.android.settingslib.core.instrumentation.VisibilityLoggerMixin; 40 import com.android.settingslib.core.lifecycle.ObservablePreferenceFragment; 41 42 /** 43 * Instrumented fragment that logs visibility state. 44 */ 45 public abstract class InstrumentedPreferenceFragment extends ObservablePreferenceFragment 46 implements Instrumentable { 47 48 private static final String TAG = "InstrumentedPrefFrag"; 49 50 protected MetricsFeatureProvider mMetricsFeatureProvider; 51 52 // metrics placeholder value. Only use this for development. 53 protected final int PLACEHOLDER_METRIC = 10000; 54 55 private VisibilityLoggerMixin mVisibilityLoggerMixin; 56 private RecyclerView.OnScrollListener mOnScrollListener; 57 58 @Override onAttach(Context context)59 public void onAttach(Context context) { 60 mMetricsFeatureProvider = FeatureFactory.getFactory(context).getMetricsFeatureProvider(); 61 // Mixin that logs visibility change for activity. 62 mVisibilityLoggerMixin = new VisibilityLoggerMixin(getMetricsCategory(), 63 mMetricsFeatureProvider); 64 getSettingsLifecycle().addObserver(mVisibilityLoggerMixin); 65 getSettingsLifecycle().addObserver(new SurveyMixin(this, getClass().getSimpleName())); 66 super.onAttach(context); 67 } 68 69 @Override onStart()70 public void onStart() { 71 super.onStart(); 72 // Override the OnPreferenceTreeClickListener in super.onStart() to inject jank detection. 73 getPreferenceManager().setOnPreferenceTreeClickListener((preference) -> { 74 if (preference instanceof SwitchPreference) { 75 SettingsJankMonitor.detectSwitchPreferenceClickJank( 76 getListView(), (SwitchPreference) preference); 77 } 78 return onPreferenceTreeClick(preference); 79 }); 80 } 81 82 @Override onResume()83 public void onResume() { 84 mVisibilityLoggerMixin.setSourceMetricsCategory(getActivity()); 85 // Add scroll listener to trace interaction jank. 86 final RecyclerView recyclerView = getListView(); 87 if (recyclerView != null) { 88 mOnScrollListener = new OnScrollListener(getClass().getName()); 89 recyclerView.addOnScrollListener(mOnScrollListener); 90 } 91 super.onResume(); 92 } 93 94 @Override onPause()95 public void onPause() { 96 final RecyclerView recyclerView = getListView(); 97 if (mOnScrollListener != null) { 98 recyclerView.removeOnScrollListener(mOnScrollListener); 99 mOnScrollListener = null; 100 } 101 super.onPause(); 102 } 103 104 @Override onCreatePreferences(Bundle savedInstanceState, String rootKey)105 public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { 106 final int resId = getPreferenceScreenResId(); 107 if (resId > 0) { 108 addPreferencesFromResource(resId); 109 } 110 } 111 112 @Override addPreferencesFromResource(@mlRes int preferencesResId)113 public void addPreferencesFromResource(@XmlRes int preferencesResId) { 114 super.addPreferencesFromResource(preferencesResId); 115 updateActivityTitleWithScreenTitle(getPreferenceScreen()); 116 } 117 118 @Override findPreference(CharSequence key)119 public <T extends Preference> T findPreference(CharSequence key) { 120 if (key == null) { 121 return null; 122 } 123 return super.findPreference(key); 124 } 125 126 @Override onPreferenceTreeClick(Preference preference)127 public boolean onPreferenceTreeClick(Preference preference) { 128 writePreferenceClickMetric(preference); 129 return super.onPreferenceTreeClick(preference); 130 } 131 getPrefContext()132 protected final Context getPrefContext() { 133 return getPreferenceManager().getContext(); 134 } 135 136 /** 137 * Get the res id for static preference xml for this fragment. 138 */ getPreferenceScreenResId()139 protected int getPreferenceScreenResId() { 140 return -1; 141 } 142 writeElapsedTimeMetric(int action, String key)143 protected void writeElapsedTimeMetric(int action, String key) { 144 mVisibilityLoggerMixin.writeElapsedTimeMetric(action, key); 145 } 146 writePreferenceClickMetric(Preference preference)147 protected void writePreferenceClickMetric(Preference preference) { 148 mMetricsFeatureProvider.logClickedPreference(preference, getMetricsCategory()); 149 } 150 updateActivityTitleWithScreenTitle(PreferenceScreen screen)151 private void updateActivityTitleWithScreenTitle(PreferenceScreen screen) { 152 if (screen != null) { 153 final CharSequence title = screen.getTitle(); 154 if (!TextUtils.isEmpty(title)) { 155 getActivity().setTitle(title); 156 } else { 157 Log.w(TAG, "Screen title missing for fragment " + this.getClass().getName()); 158 } 159 } 160 } 161 162 private static final class OnScrollListener extends RecyclerView.OnScrollListener { 163 private final InteractionJankMonitor mMonitor = InteractionJankMonitor.getInstance(); 164 private final String mClassName; 165 OnScrollListener(String className)166 private OnScrollListener(String className) { 167 mClassName = className; 168 } 169 170 @Override onScrollStateChanged(RecyclerView recyclerView, int newState)171 public void onScrollStateChanged(RecyclerView recyclerView, int newState) { 172 switch (newState) { 173 case RecyclerView.SCROLL_STATE_DRAGGING: 174 final Configuration.Builder builder = 175 Configuration.Builder.withView(CUJ_SETTINGS_PAGE_SCROLL, recyclerView) 176 .setTag(mClassName); 177 mMonitor.begin(builder); 178 break; 179 case RecyclerView.SCROLL_STATE_IDLE: 180 mMonitor.end(CUJ_SETTINGS_PAGE_SCROLL); 181 break; 182 default: 183 } 184 } 185 } 186 } 187