1 /* 2 * Copyright (C) 2019 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.twopanelsettings.slices; 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.twopanelsettings.slices.InstrumentationUtils.logPageFocused; 27 28 import android.animation.AnimatorInflater; 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.KeyEvent; 34 import android.view.Menu; 35 import android.view.MenuInflater; 36 import android.view.MenuItem; 37 import android.view.MotionEvent; 38 import android.view.View; 39 import android.view.ViewGroup; 40 import android.widget.TextView; 41 42 import androidx.annotation.CallSuper; 43 import androidx.annotation.NonNull; 44 import androidx.lifecycle.LifecycleOwner; 45 import androidx.preference.PreferenceGroupAdapter; 46 import androidx.preference.PreferenceScreen; 47 import androidx.preference.PreferenceViewHolder; 48 import androidx.recyclerview.widget.RecyclerView; 49 50 import com.android.settingslib.core.lifecycle.Lifecycle; 51 import com.android.tv.twopanelsettings.R; 52 import com.android.tv.twopanelsettings.SettingsPreferenceFragmentBase; 53 import com.android.tv.twopanelsettings.TwoPanelSettingsFragment; 54 55 /** 56 * A copy of SettingsPreferenceFragment in Settings. 57 */ 58 public abstract class SettingsPreferenceFragment extends SettingsPreferenceFragmentBase 59 implements LifecycleOwner, 60 TwoPanelSettingsFragment.PreviewableComponentCallback { 61 private final Lifecycle mLifecycle = new Lifecycle(this); 62 63 // Rename getLifecycle() to getSettingsLifecycle() as androidx Fragment has already implemented 64 // getLifecycle(), overriding here would cause unexpected crash in framework. 65 @NonNull getSettingsLifecycle()66 public Lifecycle getSettingsLifecycle() { 67 return mLifecycle; 68 } 69 SettingsPreferenceFragment()70 public SettingsPreferenceFragment() { 71 } 72 73 @CallSuper 74 @Override onAttach(Context context)75 public void onAttach(Context context) { 76 super.onAttach(context); 77 mLifecycle.onAttach(context); 78 } 79 80 @CallSuper 81 @Override onCreate(Bundle savedInstanceState)82 public void onCreate(Bundle savedInstanceState) { 83 mLifecycle.onCreate(savedInstanceState); 84 mLifecycle.handleLifecycleEvent(ON_CREATE); 85 super.onCreate(savedInstanceState); 86 if (getCallbackFragment() != null 87 && !(getCallbackFragment() instanceof TwoPanelSettingsFragment)) { 88 logPageFocused(getPageId(), true); 89 } 90 } 91 92 // We explicitly set the title gravity to RIGHT in RTL cases to remedy some complicated gravity 93 // issues. For more details, please read the comment of onViewCreated() in 94 // com.android.tv.settings.SettingsPreferenceFragment. 95 @Override onViewCreated(View view, Bundle savedInstanceState)96 public void onViewCreated(View view, Bundle savedInstanceState) { 97 super.onViewCreated(view, savedInstanceState); 98 if (view != null) { 99 TextView titleView = view.findViewById(R.id.decor_title); 100 // We rely on getResources().getConfiguration().getLayoutDirection() instead of 101 // view.isLayoutRtl() as the latter could return false in some complex scenarios even if 102 // it is RTL. 103 if (titleView != null 104 && getResources().getConfiguration().getLayoutDirection() 105 == View.LAYOUT_DIRECTION_RTL) { 106 titleView.setGravity(Gravity.RIGHT); 107 } 108 } 109 } 110 111 @Override onCreateAdapter(PreferenceScreen preferenceScreen)112 protected RecyclerView.Adapter onCreateAdapter(PreferenceScreen preferenceScreen) { 113 return new PreferenceGroupAdapter(preferenceScreen) { 114 @Override 115 @NonNull 116 public PreferenceViewHolder onCreateViewHolder(@NonNull ViewGroup parent, 117 int viewType) { 118 PreferenceViewHolder vh = super.onCreateViewHolder(parent, viewType); 119 vh.itemView.setStateListAnimator(AnimatorInflater.loadStateListAnimator( 120 getContext(), R.animator.preference)); 121 vh.itemView.setOnTouchListener((v, e) -> { 122 if (e.getActionMasked() == MotionEvent.ACTION_DOWN 123 && isPrimaryKey(e.getButtonState())) { 124 vh.itemView.requestFocus(); 125 v.dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, 126 KeyEvent.KEYCODE_DPAD_CENTER)); 127 return true; 128 } else if (e.getActionMasked() == MotionEvent.ACTION_UP 129 && isPrimaryKey(e.getButtonState())) { 130 v.dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, 131 KeyEvent.KEYCODE_DPAD_CENTER)); 132 return true; 133 } 134 return false; 135 }); 136 vh.itemView.setFocusable(true); 137 vh.itemView.setFocusableInTouchMode(true); 138 return vh; 139 } 140 }; 141 } 142 143 @Override 144 public void setPreferenceScreen(PreferenceScreen preferenceScreen) { 145 mLifecycle.setPreferenceScreen(preferenceScreen); 146 super.setPreferenceScreen(preferenceScreen); 147 } 148 149 @CallSuper 150 @Override 151 public void onSaveInstanceState(Bundle outState) { 152 super.onSaveInstanceState(outState); 153 mLifecycle.onSaveInstanceState(outState); 154 } 155 156 @CallSuper 157 @Override 158 public void onStart() { 159 mLifecycle.handleLifecycleEvent(ON_START); 160 super.onStart(); 161 } 162 163 @CallSuper 164 @Override 165 public void onResume() { 166 super.onResume(); 167 mLifecycle.handleLifecycleEvent(ON_RESUME); 168 } 169 170 // This should only be invoked if the parent Fragment is TwoPanelSettingsFragment. 171 @CallSuper 172 @Override 173 public void onArriveAtMainPanel(boolean forward) { 174 logPageFocused(getPageId(), forward); 175 } 176 177 @CallSuper 178 @Override 179 public void onPause() { 180 mLifecycle.handleLifecycleEvent(ON_PAUSE); 181 super.onPause(); 182 } 183 184 @CallSuper 185 @Override 186 public void onStop() { 187 mLifecycle.handleLifecycleEvent(ON_STOP); 188 super.onStop(); 189 } 190 191 @CallSuper 192 @Override 193 public void onDestroy() { 194 mLifecycle.handleLifecycleEvent(ON_DESTROY); 195 super.onDestroy(); 196 } 197 198 @CallSuper 199 @Override 200 public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) { 201 mLifecycle.onCreateOptionsMenu(menu, inflater); 202 super.onCreateOptionsMenu(menu, inflater); 203 } 204 205 @CallSuper 206 @Override 207 public void onPrepareOptionsMenu(final Menu menu) { 208 mLifecycle.onPrepareOptionsMenu(menu); 209 super.onPrepareOptionsMenu(menu); 210 } 211 212 @CallSuper 213 @Override 214 public boolean onOptionsItemSelected(final MenuItem menuItem) { 215 boolean lifecycleHandled = mLifecycle.onOptionsItemSelected(menuItem); 216 if (!lifecycleHandled) { 217 return super.onOptionsItemSelected(menuItem); 218 } 219 return lifecycleHandled; 220 } 221 222 /** Subclasses should override this to use their own PageId for statsd logging. */ 223 protected int getPageId() { 224 return TvSettingsEnums.PAGE_CLASSIC_DEFAULT; 225 } 226 227 // check if such motion event should translate to key event DPAD_CENTER 228 private boolean isPrimaryKey(int buttonState) { 229 return buttonState == MotionEvent.BUTTON_PRIMARY 230 || buttonState == MotionEvent.BUTTON_STYLUS_PRIMARY 231 || buttonState == 0; // motion events which creates by UI Automator 232 } 233 } 234