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