1 /* 2 * Copyright (C) 2017 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.tv.settings; 18 19 import static androidx.lifecycle.Lifecycle.Event.ON_CREATE; 20 import static androidx.lifecycle.Lifecycle.Event.ON_DESTROY; 21 import static androidx.lifecycle.Lifecycle.Event.ON_PAUSE; 22 import static androidx.lifecycle.Lifecycle.Event.ON_RESUME; 23 import static androidx.lifecycle.Lifecycle.Event.ON_START; 24 import static androidx.lifecycle.Lifecycle.Event.ON_STOP; 25 26 import static com.android.tv.settings.util.InstrumentationUtils.logPageFocused; 27 28 import android.annotation.CallSuper; 29 import android.app.tvsettings.TvSettingsEnums; 30 import android.content.Context; 31 import android.os.Bundle; 32 import android.view.Gravity; 33 import android.view.Menu; 34 import android.view.MenuInflater; 35 import android.view.MenuItem; 36 import android.view.View; 37 import android.widget.TextView; 38 39 import androidx.annotation.NonNull; 40 import androidx.leanback.preference.LeanbackPreferenceFragment; 41 import androidx.lifecycle.LifecycleOwner; 42 import androidx.preference.PreferenceScreen; 43 44 import com.android.settingslib.core.instrumentation.Instrumentable; 45 import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; 46 import com.android.settingslib.core.instrumentation.VisibilityLoggerMixin; 47 import com.android.settingslib.core.lifecycle.Lifecycle; 48 import com.android.tv.twopanelsettings.TwoPanelSettingsFragment; 49 50 /** 51 * A {@link LeanbackPreferenceFragment} that has hooks to observe fragment lifecycle events 52 * and allow for instrumentation. 53 */ 54 public abstract class SettingsPreferenceFragment extends LeanbackPreferenceFragment 55 implements LifecycleOwner, Instrumentable, 56 TwoPanelSettingsFragment.PreviewableComponentCallback { 57 private final Lifecycle mLifecycle = new Lifecycle(this); 58 private final VisibilityLoggerMixin mVisibilityLoggerMixin; 59 protected MetricsFeatureProvider mMetricsFeatureProvider; 60 61 @NonNull getLifecycle()62 public Lifecycle getLifecycle() { 63 return mLifecycle; 64 } 65 SettingsPreferenceFragment()66 public SettingsPreferenceFragment() { 67 mMetricsFeatureProvider = new MetricsFeatureProvider(); 68 // Mixin that logs visibility change for activity. 69 mVisibilityLoggerMixin = new VisibilityLoggerMixin(getMetricsCategory(), 70 mMetricsFeatureProvider); 71 getLifecycle().addObserver(mVisibilityLoggerMixin); 72 } 73 74 @CallSuper 75 @Override onAttach(Context context)76 public void onAttach(Context context) { 77 super.onAttach(context); 78 mLifecycle.onAttach(context); 79 } 80 81 @CallSuper 82 @Override onCreate(Bundle savedInstanceState)83 public void onCreate(Bundle savedInstanceState) { 84 mLifecycle.onCreate(savedInstanceState); 85 mLifecycle.handleLifecycleEvent(ON_CREATE); 86 super.onCreate(savedInstanceState); 87 if (getCallbackFragment() != null 88 && !(getCallbackFragment() instanceof TwoPanelSettingsFragment)) { 89 logPageFocused(getPageId(), true); 90 } 91 } 92 93 // While the default of relying on text language to determine gravity works well in general, 94 // some page titles (e.g., SSID as Wifi details page title) are dynamic and can be in different 95 // languages. This can cause some complex gravity issues. For example, Wifi details page in RTL 96 // showing an English SSID title would by default align the title to the left, which is 97 // incorrectly considered as START in RTL. 98 // We explicitly set the title gravity to RIGHT in RTL cases to remedy this issue. 99 @Override onViewCreated(View view, Bundle savedInstanceState)100 public void onViewCreated(View view, Bundle savedInstanceState) { 101 super.onViewCreated(view, savedInstanceState); 102 if (view != null) { 103 TextView titleView = view.findViewById(R.id.decor_title); 104 // We rely on getResources().getConfiguration().getLayoutDirection() instead of 105 // view.isLayoutRtl() as the latter could return false in some complex scenarios even if 106 // it is RTL. 107 if (titleView != null 108 && getResources().getConfiguration().getLayoutDirection() 109 == View.LAYOUT_DIRECTION_RTL) { 110 titleView.setGravity(Gravity.RIGHT); 111 } 112 } 113 } 114 115 @Override setPreferenceScreen(PreferenceScreen preferenceScreen)116 public void setPreferenceScreen(PreferenceScreen preferenceScreen) { 117 mLifecycle.setPreferenceScreen(preferenceScreen); 118 super.setPreferenceScreen(preferenceScreen); 119 } 120 121 @CallSuper 122 @Override onSaveInstanceState(Bundle outState)123 public void onSaveInstanceState(Bundle outState) { 124 super.onSaveInstanceState(outState); 125 mLifecycle.onSaveInstanceState(outState); 126 } 127 128 @CallSuper 129 @Override onStart()130 public void onStart() { 131 mLifecycle.handleLifecycleEvent(ON_START); 132 super.onStart(); 133 } 134 135 @CallSuper 136 @Override onResume()137 public void onResume() { 138 mVisibilityLoggerMixin.setSourceMetricsCategory(getActivity()); 139 super.onResume(); 140 mLifecycle.handleLifecycleEvent(ON_RESUME); 141 if (getCallbackFragment() instanceof TwoPanelSettingsFragment) { 142 TwoPanelSettingsFragment parentFragment = 143 (TwoPanelSettingsFragment) getCallbackFragment(); 144 parentFragment.addListenerForFragment(this); 145 } 146 } 147 148 // This should only be invoked if the parent Fragment is TwoPanelSettingsFragment. 149 @CallSuper 150 @Override onArriveAtMainPanel(boolean forward)151 public void onArriveAtMainPanel(boolean forward) { 152 logPageFocused(getPageId(), forward); 153 } 154 155 @CallSuper 156 @Override onPause()157 public void onPause() { 158 mLifecycle.handleLifecycleEvent(ON_PAUSE); 159 super.onPause(); 160 if (getCallbackFragment() instanceof TwoPanelSettingsFragment) { 161 TwoPanelSettingsFragment parentFragment = 162 (TwoPanelSettingsFragment) getCallbackFragment(); 163 parentFragment.removeListenerForFragment(this); 164 } 165 } 166 167 @CallSuper 168 @Override onStop()169 public void onStop() { 170 mLifecycle.handleLifecycleEvent(ON_STOP); 171 super.onStop(); 172 } 173 174 @CallSuper 175 @Override onDestroy()176 public void onDestroy() { 177 mLifecycle.handleLifecycleEvent(ON_DESTROY); 178 super.onDestroy(); 179 } 180 181 @CallSuper 182 @Override onCreateOptionsMenu(final Menu menu, final MenuInflater inflater)183 public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) { 184 mLifecycle.onCreateOptionsMenu(menu, inflater); 185 super.onCreateOptionsMenu(menu, inflater); 186 } 187 188 @CallSuper 189 @Override onPrepareOptionsMenu(final Menu menu)190 public void onPrepareOptionsMenu(final Menu menu) { 191 mLifecycle.onPrepareOptionsMenu(menu); 192 super.onPrepareOptionsMenu(menu); 193 } 194 195 @CallSuper 196 @Override onOptionsItemSelected(final MenuItem menuItem)197 public boolean onOptionsItemSelected(final MenuItem menuItem) { 198 boolean lifecycleHandled = mLifecycle.onOptionsItemSelected(menuItem); 199 if (!lifecycleHandled) { 200 return super.onOptionsItemSelected(menuItem); 201 } 202 return lifecycleHandled; 203 } 204 205 /** Subclasses should override this to use their own PageId for statsd logging. */ getPageId()206 protected int getPageId() { 207 return TvSettingsEnums.PAGE_CLASSIC_DEFAULT; 208 } 209 } 210