• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 android.app.slice.Slice.EXTRA_SLIDER_VALUE;
20 import static android.app.slice.Slice.EXTRA_TOGGLE_STATE;
21 import static android.app.slice.Slice.HINT_PARTIAL;
22 
23 import static com.android.tv.twopanelsettings.slices.InstrumentationUtils.logEntrySelected;
24 import static com.android.tv.twopanelsettings.slices.InstrumentationUtils.logToggleInteracted;
25 import static com.android.tv.twopanelsettings.slices.SlicesConstants.EXTRA_PREFERENCE_INFO_STATUS;
26 import static com.android.tv.twopanelsettings.slices.SlicesConstants.EXTRA_PREFERENCE_KEY;
27 import static com.android.tv.twopanelsettings.slices.SlicesConstants.EXTRA_SLICE_FOLLOWUP;
28 
29 import android.app.Activity;
30 import android.app.PendingIntent;
31 import android.app.PendingIntent.CanceledException;
32 import android.app.tvsettings.TvSettingsEnums;
33 import android.content.ContentProviderClient;
34 import android.content.Intent;
35 import android.content.IntentSender;
36 import android.database.ContentObserver;
37 import android.graphics.drawable.Icon;
38 import android.net.Uri;
39 import android.os.Bundle;
40 import android.os.Handler;
41 import android.os.Parcelable;
42 import android.text.TextUtils;
43 import android.util.Log;
44 import android.util.TypedValue;
45 import android.view.ContextThemeWrapper;
46 import android.view.Gravity;
47 import android.view.LayoutInflater;
48 import android.view.View;
49 import android.view.ViewGroup;
50 import android.widget.ImageView;
51 import android.widget.TextView;
52 
53 import androidx.activity.result.ActivityResult;
54 import androidx.activity.result.ActivityResultCallback;
55 import androidx.activity.result.ActivityResultLauncher;
56 import androidx.activity.result.IntentSenderRequest;
57 import androidx.activity.result.contract.ActivityResultContracts;
58 import androidx.annotation.Keep;
59 import androidx.annotation.NonNull;
60 import androidx.lifecycle.Observer;
61 import androidx.preference.Preference;
62 import androidx.preference.PreferenceScreen;
63 import androidx.preference.TwoStatePreference;
64 import androidx.slice.Slice;
65 import androidx.slice.SliceItem;
66 import androidx.slice.widget.ListContent;
67 import androidx.slice.widget.SliceContent;
68 
69 import com.android.tv.twopanelsettings.R;
70 import com.android.tv.twopanelsettings.TwoPanelSettingsFragment;
71 import com.android.tv.twopanelsettings.TwoPanelSettingsFragment.SliceFragmentCallback;
72 import com.android.tv.twopanelsettings.slices.PreferenceSliceLiveData.SliceLiveDataImpl;
73 import com.android.tv.twopanelsettings.slices.SlicePreferencesUtil.Data;
74 
75 import java.util.ArrayList;
76 import java.util.List;
77 
78 /**
79  * A screen presenting a slice in TV settings.
80  */
81 @Keep
82 public class SliceFragment extends SettingsPreferenceFragment implements Observer<Slice>,
83         SliceFragmentCallback {
84     private static final int SLICE_REQUEST_CODE = 10000;
85     private static final String TAG = "SliceFragment";
86     private static final String KEY_PREFERENCE_FOLLOWUP_INTENT = "key_preference_followup_intent";
87     private static final String KEY_PREFERENCE_FOLLOWUP_RESULT_CODE =
88             "key_preference_followup_result_code";
89     private static final String KEY_SCREEN_TITLE = "key_screen_title";
90     private static final String KEY_SCREEN_SUBTITLE = "key_screen_subtitle";
91     private static final String KEY_SCREEN_ICON = "key_screen_icon";
92     private static final String KEY_LAST_PREFERENCE = "key_last_preference";
93     private static final String KEY_URI_STRING = "key_uri_string";
94     private ListContent mListContent;
95     private Slice mSlice;
96     private ContextThemeWrapper mContextThemeWrapper;
97     private String mUriString = null;
98     private int mCurrentPageId;
99     private CharSequence mScreenTitle;
100     private CharSequence mScreenSubtitle;
101     private Icon mScreenIcon;
102     private PendingIntent mPreferenceFollowupIntent;
103     private int mFollowupPendingIntentResultCode;
104     private Intent mFollowupPendingIntentExtras;
105     private Intent mFollowupPendingIntentExtrasCopy;
106     private String mLastFocusedPreferenceKey;
107     private boolean mIsMainPanelReady = true;
108 
109     private final Handler mHandler = new Handler();
110     private final ActivityResultLauncher<IntentSenderRequest> mActivityResultLauncher =
111             registerForActivityResult(new ActivityResultContracts.StartIntentSenderForResult(),
112                     new ActivityResultCallback<ActivityResult>() {
113                         @Override
114                         public void onActivityResult(ActivityResult result) {
115                             Intent data = result.getData();
116                             mFollowupPendingIntentExtras = data;
117                             mFollowupPendingIntentExtrasCopy = data == null ? null : new Intent(
118                                     data);
119                             mFollowupPendingIntentResultCode = result.getResultCode();
120                         }
121                     });
122     private final ContentObserver mContentObserver = new ContentObserver(new Handler()) {
123         @Override
124         public void onChange(boolean selfChange, Uri uri) {
125             handleUri(uri);
126             super.onChange(selfChange, uri);
127         }
128     };
129 
130     /** Callback for one panel settings fragment **/
131     public interface OnePanelSliceFragmentContainer {
navigateBack()132         void navigateBack();
133     }
134 
135     @Override
onCreate(Bundle savedInstanceState)136     public void onCreate(Bundle savedInstanceState) {
137         mUriString = getArguments().getString(SlicesConstants.TAG_TARGET_URI);
138         ContextSingleton.getInstance().grantFullAccess(getContext(), Uri.parse(mUriString));
139         super.onCreate(savedInstanceState);
140     }
141 
142     @Override
onResume()143     public void onResume() {
144         this.setTitle(mScreenTitle);
145         this.setSubtitle(mScreenSubtitle);
146         this.setIcon(mScreenIcon);
147         this.getPreferenceScreen().removeAll();
148 
149         showProgressBar();
150         getSliceLiveData().observeForever(this);
151         if (TextUtils.isEmpty(mScreenTitle)) {
152             mScreenTitle = getArguments().getCharSequence(SlicesConstants.TAG_SCREEN_TITLE, "");
153         }
154         super.onResume();
155         getContext().getContentResolver().registerContentObserver(
156                 SlicePreferencesUtil.getStatusPath(mUriString), false, mContentObserver);
157         fireFollowupPendingIntent();
158     }
159 
getSliceLiveData()160     private SliceLiveDataImpl getSliceLiveData() {
161         return ContextSingleton.getInstance()
162                 .getSliceLiveData(getActivity(), Uri.parse(mUriString));
163     }
164 
fireFollowupPendingIntent()165     private void fireFollowupPendingIntent() {
166         if (mFollowupPendingIntentExtras == null) {
167             return;
168         }
169         // If there is followup pendingIntent returned from initial activity, send it.
170         // Otherwise send the followup pendingIntent provided by slice api.
171         Parcelable followupPendingIntent;
172         try {
173             followupPendingIntent = mFollowupPendingIntentExtrasCopy.getParcelableExtra(
174                     EXTRA_SLICE_FOLLOWUP);
175         } catch (Throwable ex) {
176             // unable to parse, the Intent has custom Parcelable, fallback
177             followupPendingIntent = null;
178         }
179         if (followupPendingIntent instanceof PendingIntent) {
180             try {
181                 ((PendingIntent) followupPendingIntent).send();
182             } catch (CanceledException e) {
183                 Log.e(TAG, "Followup PendingIntent for slice cannot be sent", e);
184             }
185         } else {
186             if (mPreferenceFollowupIntent == null) {
187                 return;
188             }
189             try {
190                 mPreferenceFollowupIntent.send(getContext(),
191                         mFollowupPendingIntentResultCode, mFollowupPendingIntentExtras);
192             } catch (CanceledException e) {
193                 Log.e(TAG, "Followup PendingIntent for slice cannot be sent", e);
194             }
195             mPreferenceFollowupIntent = null;
196         }
197     }
198 
199     @Override
onPause()200     public void onPause() {
201         super.onPause();
202         hideProgressBar();
203         getContext().getContentResolver().unregisterContentObserver(mContentObserver);
204         getSliceLiveData().removeObserver(this);
205     }
206 
207     @Override
onCreatePreferences(Bundle savedInstanceState, String rootKey)208     public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
209         PreferenceScreen preferenceScreen = getPreferenceManager()
210                 .createPreferenceScreen(getContext());
211         setPreferenceScreen(preferenceScreen);
212 
213         TypedValue themeTypedValue = new TypedValue();
214         getActivity().getTheme().resolveAttribute(R.attr.preferenceTheme, themeTypedValue, true);
215         mContextThemeWrapper = new ContextThemeWrapper(getActivity(), themeTypedValue.resourceId);
216 
217     }
218 
isUriValid(String uri)219     private boolean isUriValid(String uri) {
220         if (uri == null) {
221             return false;
222         }
223         ContentProviderClient client =
224                 getContext().getContentResolver().acquireContentProviderClient(Uri.parse(uri));
225         if (client != null) {
226             client.close();
227             return true;
228         } else {
229             return false;
230         }
231     }
232 
update()233     private void update() {
234         mListContent = new ListContent(mSlice);
235         PreferenceScreen preferenceScreen =
236                 getPreferenceManager().getPreferenceScreen();
237 
238         if (preferenceScreen == null) {
239             return;
240         }
241 
242         List<SliceContent> items = mListContent.getRowItems();
243         if (items == null || items.size() == 0) {
244             return;
245         }
246 
247         SliceItem redirectSliceItem = SlicePreferencesUtil.getRedirectSlice(items);
248         String redirectSlice = null;
249         if (redirectSliceItem != null) {
250             Data data = SlicePreferencesUtil.extract(redirectSliceItem);
251             CharSequence title = SlicePreferencesUtil.getText(data.mTitleItem);
252             if (!TextUtils.isEmpty(title)) {
253                 redirectSlice = title.toString();
254             }
255         }
256         if (isUriValid(redirectSlice)) {
257             getSliceLiveData().removeObserver(this);
258             getContext().getContentResolver().unregisterContentObserver(mContentObserver);
259             mUriString = redirectSlice;
260             getSliceLiveData().observeForever(this);
261             getContext().getContentResolver().registerContentObserver(
262                     SlicePreferencesUtil.getStatusPath(mUriString), false, mContentObserver);
263         }
264 
265         SliceItem screenTitleItem = SlicePreferencesUtil.getScreenTitleItem(items);
266         if (screenTitleItem == null) {
267             setTitle(mScreenTitle);
268         } else {
269             Data data = SlicePreferencesUtil.extract(screenTitleItem);
270             mCurrentPageId = SlicePreferencesUtil.getPageId(screenTitleItem);
271             CharSequence title = SlicePreferencesUtil.getText(data.mTitleItem);
272             if (!TextUtils.isEmpty(title)) {
273                 setTitle(title);
274                 mScreenTitle = title;
275             } else {
276                 setTitle(mScreenTitle);
277             }
278 
279             CharSequence subtitle = SlicePreferencesUtil.getText(data.mSubtitleItem);
280             setSubtitle(subtitle);
281 
282             Icon icon = SlicePreferencesUtil.getIcon(data.mStartItem);
283             setIcon(icon);
284         }
285 
286         SliceItem focusedPrefItem = SlicePreferencesUtil.getFocusedPreferenceItem(items);
287         CharSequence defaultFocusedKey = null;
288         if (focusedPrefItem != null) {
289             Data data = SlicePreferencesUtil.extract(focusedPrefItem);
290             CharSequence title = SlicePreferencesUtil.getText(data.mTitleItem);
291             if (!TextUtils.isEmpty(title)) {
292                 defaultFocusedKey = title;
293             }
294         }
295 
296         List<Preference> newPrefs = new ArrayList<>();
297         for (SliceContent contentItem : items) {
298             SliceItem item = contentItem.getSliceItem();
299             if (SlicesConstants.TYPE_PREFERENCE.equals(item.getSubType())
300                     || SlicesConstants.TYPE_PREFERENCE_CATEGORY.equals(item.getSubType())) {
301                 Preference preference =
302                         SlicePreferencesUtil.getPreference(
303                                 item, mContextThemeWrapper, getClass().getCanonicalName(),
304                                 getParentFragment() instanceof TwoPanelSettingsFragment);
305                 if (preference != null) {
306                     newPrefs.add(preference);
307                 }
308             }
309         }
310         updatePreferenceScreen(preferenceScreen, newPrefs);
311         if (defaultFocusedKey != null) {
312             scrollToPreference(defaultFocusedKey.toString());
313         } else if (mLastFocusedPreferenceKey != null) {
314             scrollToPreference(mLastFocusedPreferenceKey);
315         }
316 
317         if (getParentFragment() instanceof TwoPanelSettingsFragment) {
318             ((TwoPanelSettingsFragment) getParentFragment()).refocusPreference(this);
319         }
320         mIsMainPanelReady = true;
321     }
322 
323 
back()324     private void back() {
325         if (getCallbackFragment() instanceof TwoPanelSettingsFragment) {
326             TwoPanelSettingsFragment parentFragment =
327                     (TwoPanelSettingsFragment) getCallbackFragment();
328             if (parentFragment.isFragmentInTheMainPanel(this)) {
329                 parentFragment.navigateBack();
330             }
331         } else if (getCallbackFragment() instanceof OnePanelSliceFragmentContainer) {
332             ((OnePanelSliceFragmentContainer) getCallbackFragment()).navigateBack();
333         }
334     }
335 
forward()336     private void forward() {
337         if (mIsMainPanelReady) {
338             if (getCallbackFragment() instanceof TwoPanelSettingsFragment) {
339                 TwoPanelSettingsFragment parentFragment =
340                         (TwoPanelSettingsFragment) getCallbackFragment();
341                 Preference chosenPreference = TwoPanelSettingsFragment.getChosenPreference(this);
342                 if (chosenPreference == null && mLastFocusedPreferenceKey != null) {
343                     chosenPreference = findPreference(mLastFocusedPreferenceKey);
344                 }
345                 if (chosenPreference != null && chosenPreference instanceof HasSliceUri
346                         && ((HasSliceUri) chosenPreference).getUri() != null) {
347                     chosenPreference.setFragment(SliceFragment.class.getCanonicalName());
348                     parentFragment.refocusPreferenceForceRefresh(chosenPreference);
349                 }
350                 if (parentFragment.isFragmentInTheMainPanel(this)) {
351                     parentFragment.navigateToPreviewFragment();
352                 }
353             }
354         } else {
355             mHandler.post(() -> forward());
356         }
357     }
358 
updatePreferenceScreen(PreferenceScreen screen, List<Preference> newPrefs)359     private void updatePreferenceScreen(PreferenceScreen screen, List<Preference> newPrefs) {
360         // Remove all the preferences in the screen that satisfy such two cases:
361         // (a) Preference without key
362         // (b) Preference with key which does not appear in the new list.
363         int index = 0;
364         while (index < screen.getPreferenceCount()) {
365             boolean needToRemoveCurrentPref = true;
366             Preference oldPref = screen.getPreference(index);
367             if (oldPref != null && oldPref.getKey() != null) {
368                 for (Preference newPref : newPrefs) {
369                     if (newPref.getKey() != null && newPref.getKey().equals(oldPref.getKey())) {
370                         needToRemoveCurrentPref = false;
371                         break;
372                     }
373                 }
374             }
375             if (needToRemoveCurrentPref) {
376                 screen.removePreference(oldPref);
377             } else {
378                 index++;
379             }
380         }
381 
382         //Iterate the new preferences list and give each preference a correct order
383         for (int i = 0; i < newPrefs.size(); i++) {
384             Preference newPref = newPrefs.get(i);
385             boolean neededToAddNewPref = true;
386             // If the newPref has a key and has a corresponding old preference, update the old
387             // preference and give it a new order.
388             if (newPref.getKey() != null) {
389                 for (int j = 0; j < screen.getPreferenceCount(); j++) {
390                     Preference oldPref = screen.getPreference(j);
391                     if (oldPref.getKey() != null && oldPref.getKey().equals(newPref.getKey())) {
392                         oldPref.setIcon(newPref.getIcon());
393                         oldPref.setTitle(newPref.getTitle());
394                         oldPref.setSummary(newPref.getSummary());
395                         oldPref.setEnabled(newPref.isEnabled());
396                         oldPref.setSelectable(newPref.isSelectable());
397                         oldPref.setFragment(newPref.getFragment());
398                         oldPref.getExtras().putAll(newPref.getExtras());
399                         if ((oldPref instanceof TwoStatePreference)
400                                 && (newPref instanceof TwoStatePreference)) {
401                             ((TwoStatePreference) oldPref)
402                                     .setChecked(((TwoStatePreference) newPref).isChecked());
403                         }
404                         if ((oldPref instanceof HasSliceAction)
405                                 && (newPref instanceof HasSliceAction)) {
406                             ((HasSliceAction) oldPref)
407                                     .setSliceAction(((HasSliceAction) newPref).getSliceAction());
408                         }
409                         if ((oldPref instanceof HasSliceUri)
410                                 && (newPref instanceof HasSliceUri)) {
411                             ((HasSliceUri) oldPref)
412                                     .setUri(((HasSliceUri) newPref).getUri());
413                         }
414                         oldPref.setOrder(i);
415                         neededToAddNewPref = false;
416                         break;
417                     }
418                 }
419             }
420             // If the newPref cannot find a corresponding old preference, or it does not have a key,
421             // add it to the screen with the correct order.
422             if (neededToAddNewPref) {
423                 newPref.setOrder(i);
424                 screen.addPreference(newPref);
425             }
426         }
427         removeAnimationClipping(getView());
428     }
429 
removeAnimationClipping(View v)430     protected void removeAnimationClipping(View v) {
431         if (v instanceof ViewGroup) {
432             ((ViewGroup) v).setClipChildren(false);
433             ((ViewGroup) v).setClipToPadding(false);
434             for (int index = 0; index < ((ViewGroup) v).getChildCount(); index++) {
435                 View child = ((ViewGroup) v).getChildAt(index);
436                 removeAnimationClipping(child);
437             }
438         }
439     }
440 
441     @Override
onPreferenceFocused(Preference preference)442     public void onPreferenceFocused(Preference preference) {
443         setLastFocused(preference);
444     }
445 
446     @Override
onSeekbarPreferenceChanged(SliceSeekbarPreference preference, int addValue)447     public void onSeekbarPreferenceChanged(SliceSeekbarPreference preference, int addValue) {
448         int curValue = preference.getValue();
449         if((addValue > 0 && curValue < preference.getMax()) ||
450            (addValue < 0 && curValue > preference.getMin())) {
451             preference.setValue(curValue + addValue);
452 
453             try {
454                 Intent fillInIntent =
455                         new Intent()
456                                 .putExtra(EXTRA_SLIDER_VALUE, preference.getValue())
457                                 .putExtra(EXTRA_PREFERENCE_KEY, preference.getKey());
458                 firePendingIntent((HasSliceAction) preference, fillInIntent);
459             } catch (Exception e) {
460                 Log.e(TAG, "PendingIntent for slice cannot be sent", e);
461             }
462         }
463     }
464 
465     @Override
onPreferenceTreeClick(Preference preference)466     public boolean onPreferenceTreeClick(Preference preference) {
467         if (preference instanceof SliceRadioPreference) {
468             SliceRadioPreference radioPref = (SliceRadioPreference) preference;
469             if (!radioPref.isChecked()) {
470                 radioPref.setChecked(true);
471                 if (TextUtils.isEmpty(radioPref.getUri())) {
472                     return true;
473                 }
474             }
475 
476             logEntrySelected(getPreferenceActionId(preference));
477             Intent fillInIntent = new Intent().putExtra(EXTRA_PREFERENCE_KEY, preference.getKey());
478 
479             boolean result = firePendingIntent(radioPref, fillInIntent);
480             radioPref.clearOtherRadioPreferences(getPreferenceScreen());
481             if (result) {
482                 return true;
483             }
484         } else if (preference instanceof TwoStatePreference
485                 && preference instanceof HasSliceAction) {
486             boolean isChecked = ((TwoStatePreference) preference).isChecked();
487             preference.getExtras().putBoolean(EXTRA_PREFERENCE_INFO_STATUS, isChecked);
488             if (getParentFragment() instanceof TwoPanelSettingsFragment) {
489                 ((TwoPanelSettingsFragment) getParentFragment()).refocusPreference(this);
490             }
491             logToggleInteracted(getPreferenceActionId(preference), isChecked);
492             Intent fillInIntent =
493                     new Intent()
494                             .putExtra(EXTRA_TOGGLE_STATE, isChecked)
495                             .putExtra(EXTRA_PREFERENCE_KEY, preference.getKey());
496             if (firePendingIntent((HasSliceAction) preference, fillInIntent)) {
497                 return true;
498             }
499             return true;
500         } else if (preference instanceof SlicePreference) {
501             // In this case, we may intentionally ignore this entry selection to avoid double
502             // logging as the action should result in a PAGE_FOCUSED event being logged.
503             if (getPreferenceActionId(preference) != TvSettingsEnums.ENTRY_DEFAULT) {
504                 logEntrySelected(getPreferenceActionId(preference));
505             }
506             Intent fillInIntent =
507                     new Intent().putExtra(EXTRA_PREFERENCE_KEY, preference.getKey());
508             if (firePendingIntent((HasSliceAction) preference, fillInIntent)) {
509                 return true;
510             }
511         }
512 
513         return super.onPreferenceTreeClick(preference);
514     }
515 
firePendingIntent(@onNull HasSliceAction preference, Intent fillInIntent)516     private boolean firePendingIntent(@NonNull HasSliceAction preference, Intent fillInIntent) {
517         if (preference.getSliceAction() == null) {
518             return false;
519         }
520         IntentSender intentSender = preference.getSliceAction().getAction().getIntentSender();
521         mActivityResultLauncher.launch(
522                 new IntentSenderRequest.Builder(intentSender).setFillInIntent(
523                         fillInIntent).build());
524         if (preference.getFollowupSliceAction() != null) {
525             mPreferenceFollowupIntent = preference.getFollowupSliceAction().getAction();
526         }
527 
528         return true;
529     }
530 
531     @Override
onSaveInstanceState(Bundle outState)532     public void onSaveInstanceState(Bundle outState) {
533         super.onSaveInstanceState(outState);
534         outState.putParcelable(KEY_PREFERENCE_FOLLOWUP_INTENT, mPreferenceFollowupIntent);
535         outState.putInt(KEY_PREFERENCE_FOLLOWUP_RESULT_CODE, mFollowupPendingIntentResultCode);
536         outState.putCharSequence(KEY_SCREEN_TITLE, mScreenTitle);
537         outState.putCharSequence(KEY_SCREEN_SUBTITLE, mScreenSubtitle);
538         outState.putParcelable(KEY_SCREEN_ICON, mScreenIcon);
539         outState.putString(KEY_LAST_PREFERENCE, mLastFocusedPreferenceKey);
540         outState.putString(KEY_URI_STRING, mUriString);
541     }
542 
543     @Override
onActivityCreated(Bundle savedInstanceState)544     public void onActivityCreated(Bundle savedInstanceState) {
545         super.onActivityCreated(savedInstanceState);
546         if (savedInstanceState != null) {
547             mPreferenceFollowupIntent =
548                     savedInstanceState.getParcelable(KEY_PREFERENCE_FOLLOWUP_INTENT);
549             mFollowupPendingIntentResultCode =
550                     savedInstanceState.getInt(KEY_PREFERENCE_FOLLOWUP_RESULT_CODE);
551             mScreenTitle = savedInstanceState.getCharSequence(KEY_SCREEN_TITLE);
552             mScreenSubtitle = savedInstanceState.getCharSequence(KEY_SCREEN_SUBTITLE);
553             mScreenIcon = savedInstanceState.getParcelable(KEY_SCREEN_ICON);
554             mLastFocusedPreferenceKey = savedInstanceState.getString(KEY_LAST_PREFERENCE);
555             mUriString = savedInstanceState.getString(KEY_URI_STRING);
556         }
557     }
558 
559     @Override
onChanged(@onNull Slice slice)560     public void onChanged(@NonNull Slice slice) {
561         mSlice = slice;
562         // Make TvSettings guard against the case that slice provider is not set up correctly
563         if (slice == null || slice.getHints() == null) {
564             return;
565         }
566 
567         if (slice.getHints().contains(HINT_PARTIAL)) {
568             showProgressBar();
569         } else {
570             hideProgressBar();
571         }
572         mIsMainPanelReady = false;
573         update();
574     }
575 
showProgressBar()576     private void showProgressBar() {
577         View view = this.getView();
578         View progressBar = view == null ? null : getView().findViewById(R.id.progress_bar);
579         if (progressBar != null) {
580             progressBar.bringToFront();
581             progressBar.setVisibility(View.VISIBLE);
582         }
583     }
584 
hideProgressBar()585     private void hideProgressBar() {
586         View view = this.getView();
587         View progressBar = view == null ? null : getView().findViewById(R.id.progress_bar);
588         if (progressBar != null) {
589             progressBar.setVisibility(View.GONE);
590         }
591     }
592 
setSubtitle(CharSequence subtitle)593     private void setSubtitle(CharSequence subtitle) {
594         View view = this.getView();
595         TextView decorSubtitle = view == null
596                 ? null
597                 : (TextView) view.findViewById(R.id.decor_subtitle);
598         if (decorSubtitle != null) {
599             // This is to remedy some complicated RTL scenario such as Hebrew RTL Account slice with
600             // English account name subtitle.
601             if (getResources().getConfiguration().getLayoutDirection()
602                     == View.LAYOUT_DIRECTION_RTL) {
603                 decorSubtitle.setGravity(Gravity.TOP | Gravity.RIGHT);
604             }
605             if (TextUtils.isEmpty(subtitle)) {
606                 decorSubtitle.setVisibility(View.GONE);
607             } else {
608                 decorSubtitle.setVisibility(View.VISIBLE);
609                 decorSubtitle.setText(subtitle);
610             }
611         }
612         mScreenSubtitle = subtitle;
613     }
614 
setIcon(Icon icon)615     private void setIcon(Icon icon) {
616         View view = this.getView();
617         ImageView decorIcon = view == null ? null : (ImageView) view.findViewById(R.id.decor_icon);
618         if (decorIcon != null && icon != null) {
619             TextView decorTitle = view.findViewById(R.id.decor_title);
620             if (decorTitle != null) {
621                 decorTitle.setMaxWidth(
622                         getResources().getDimensionPixelSize(R.dimen.decor_title_width));
623             }
624             decorIcon.setImageDrawable(icon.loadDrawable(mContextThemeWrapper));
625             decorIcon.setVisibility(View.VISIBLE);
626         } else if (decorIcon != null) {
627             decorIcon.setVisibility(View.GONE);
628         }
629         mScreenIcon = icon;
630     }
631 
632     @Override
onCreateView( LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)633     public View onCreateView(
634             LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
635         final ViewGroup view =
636                 (ViewGroup) super.onCreateView(inflater, container, savedInstanceState);
637         LayoutInflater themedInflater = LayoutInflater.from(view.getContext());
638         final View newTitleContainer = themedInflater.inflate(
639                 R.layout.slice_title_container, container, false);
640         view.removeView(view.findViewById(R.id.decor_title_container));
641         view.addView(newTitleContainer, 0);
642 
643         if (newTitleContainer != null) {
644             newTitleContainer.setOutlineProvider(null);
645             newTitleContainer.setBackgroundResource(R.color.tp_preference_panel_background_color);
646         }
647 
648         final View newContainer =
649                 themedInflater.inflate(R.layout.slice_progress_bar, container, false);
650         if (newContainer != null) {
651             ((ViewGroup) newContainer).addView(view);
652         }
653         return newContainer;
654     }
655 
setLastFocused(Preference preference)656     public void setLastFocused(Preference preference) {
657         mLastFocusedPreferenceKey = preference.getKey();
658     }
659 
handleUri(Uri uri)660     private void handleUri(Uri uri) {
661         String uriString = uri.getQueryParameter(SlicesConstants.PARAMETER_URI);
662         // Provider should provide the correct slice uri in the parameter if it wants to do certain
663         // action(includes go back, forward, error message), otherwise TvSettings would ignore it.
664         if (uriString == null || !uriString.equals(mUriString)) {
665             return;
666         }
667         String direction = uri.getQueryParameter(SlicesConstants.PARAMETER_DIRECTION);
668         if (direction != null) {
669             if (direction.equals(SlicesConstants.FORWARD)) {
670                 forward();
671             } else if (direction.equals(SlicesConstants.BACKWARD)) {
672                 back();
673             } else if (direction.equals(SlicesConstants.EXIT)) {
674                 finish();
675             }
676         }
677 
678         String errorMessage = uri.getQueryParameter(SlicesConstants.PARAMETER_ERROR);
679         if (errorMessage != null) {
680             showErrorMessage(errorMessage);
681         }
682     }
683 
finish()684     private void finish() {
685         getActivity().setResult(Activity.RESULT_OK);
686         getActivity().finish();
687     }
688 
showErrorMessage(String errorMessage)689     private void showErrorMessage(String errorMessage) {
690         if (getCallbackFragment() instanceof TwoPanelSettingsFragment) {
691             ((TwoPanelSettingsFragment) getCallbackFragment()).showErrorMessage(errorMessage, this);
692         }
693     }
694 
getPreferenceActionId(Preference preference)695     private int getPreferenceActionId(Preference preference) {
696         if (preference instanceof HasSliceAction) {
697             return ((HasSliceAction) preference).getActionId() != 0
698                     ? ((HasSliceAction) preference).getActionId()
699                     : TvSettingsEnums.ENTRY_DEFAULT;
700         }
701         return TvSettingsEnums.ENTRY_DEFAULT;
702     }
703 
getScreenTitle()704     public CharSequence getScreenTitle() {
705         return mScreenTitle;
706     }
707 
708     @Override
getPageId()709     protected int getPageId() {
710         return mCurrentPageId != 0 ? mCurrentPageId : TvSettingsEnums.PAGE_SLICE_DEFAULT;
711     }
712 
713     @Deprecated
getMetricsCategory()714     public int getMetricsCategory() {
715         return 0;
716     }
717 }
718