/*
 * Copyright (C) 2019 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.tv.twopanelsettings.slices;

import static android.app.slice.Slice.EXTRA_SLIDER_VALUE;
import static android.app.slice.Slice.EXTRA_TOGGLE_STATE;
import static android.app.slice.Slice.HINT_PARTIAL;

import static com.android.tv.twopanelsettings.slices.InstrumentationUtils.logEntrySelected;
import static com.android.tv.twopanelsettings.slices.InstrumentationUtils.logToggleInteracted;
import static com.android.tv.twopanelsettings.slices.SlicesConstants.EXTRA_PREFERENCE_INFO_STATUS;
import static com.android.tv.twopanelsettings.slices.SlicesConstants.EXTRA_PREFERENCE_KEY;
import static com.android.tv.twopanelsettings.slices.SlicesConstants.EXTRA_SLICE_FOLLOWUP;

import android.app.Activity;
import android.app.PendingIntent;
import android.app.PendingIntent.CanceledException;
import android.app.tvsettings.TvSettingsEnums;
import android.content.ContentProviderClient;
import android.content.Intent;
import android.content.IntentSender;
import android.database.ContentObserver;
import android.graphics.drawable.Icon;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Parcelable;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.Log;
import android.util.TypedValue;
import android.view.ContextThemeWrapper;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;

import androidx.activity.result.ActivityResult;
import androidx.activity.result.ActivityResultCallback;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.IntentSenderRequest;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.Keep;
import androidx.annotation.NonNull;
import androidx.lifecycle.Observer;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
import androidx.preference.TwoStatePreference;
import androidx.slice.Slice;
import androidx.slice.SliceItem;
import androidx.slice.widget.ListContent;
import androidx.slice.widget.SliceContent;

import com.android.tv.twopanelsettings.R;
import com.android.tv.twopanelsettings.TwoPanelSettingsFragment;
import com.android.tv.twopanelsettings.TwoPanelSettingsFragment.SliceFragmentCallback;
import com.android.tv.twopanelsettings.slices.PreferenceSliceLiveData.SliceLiveDataImpl;
import com.android.tv.twopanelsettings.slices.SlicePreferencesUtil.Data;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * A screen presenting a slice in TV settings.
 */
@Keep
public class SliceFragment extends SettingsPreferenceFragment implements Observer<Slice>,
        SliceFragmentCallback {
    private static final int SLICE_REQUEST_CODE = 10000;
    private static final int A11Y_FOCUS_REQUEST_DELAY = 1000;
    private static final String TAG = "SliceFragment";
    private static final String KEY_PREFERENCE_FOLLOWUP_INTENT = "key_preference_followup_intent";
    private static final String KEY_PREFERENCE_FOLLOWUP_RESULT_CODE =
            "key_preference_followup_result_code";
    private static final String KEY_SCREEN_TITLE = "key_screen_title";
    private static final String KEY_SCREEN_SUBTITLE = "key_screen_subtitle";
    private static final String KEY_SCREEN_ICON = "key_screen_icon";
    private static final String KEY_LAST_PREFERENCE = "key_last_preference";
    private static final String KEY_URI_STRING = "key_uri_string";
    private ListContent mListContent;
    private Slice mSlice;
    private ContextThemeWrapper mContextThemeWrapper;
    private String mUriString = null;
    private int mCurrentPageId;
    private CharSequence mScreenTitle;
    private CharSequence mScreenSubtitle;
    private Icon mScreenIcon;
    private PendingIntent mPreferenceFollowupIntent;
    private int mFollowupPendingIntentResultCode;
    private Intent mFollowupPendingIntentExtras;
    private Intent mFollowupPendingIntentExtrasCopy;
    private String mLastFocusedPreferenceKey;
    private boolean mIsMainPanelReady = true;

    private final Handler mHandler = new Handler();
    private final ActivityResultLauncher<IntentSenderRequest> mActivityResultLauncher =
            registerForActivityResult(new ActivityResultContracts.StartIntentSenderForResult(),
                    new ActivityResultCallback<ActivityResult>() {
                        @Override
                        public void onActivityResult(ActivityResult result) {
                            Intent data = result.getData();
                            mFollowupPendingIntentExtras = data;
                            mFollowupPendingIntentExtrasCopy = data == null ? null : new Intent(
                                    data);
                            mFollowupPendingIntentResultCode = result.getResultCode();
                        }
                    });
    private final ContentObserver mContentObserver = new ContentObserver(new Handler()) {
        @Override
        public void onChange(boolean selfChange, Uri uri) {
            handleUri(uri);
            super.onChange(selfChange, uri);
        }
    };

    /** Callback for one panel settings fragment **/
    public interface OnePanelSliceFragmentContainer {
        void navigateBack();
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        mUriString = getArguments().getString(SlicesConstants.TAG_TARGET_URI);
        if (!TextUtils.isEmpty(mUriString)) {
            ContextSingleton.getInstance().grantFullAccess(getContext(), Uri.parse(mUriString));
        }
        if (TextUtils.isEmpty(mScreenTitle)) {
            mScreenTitle = getArguments().getCharSequence(SlicesConstants.TAG_SCREEN_TITLE, "");
        }
        super.onCreate(savedInstanceState);
    }

    @Override
    public void onResume() {
        this.setTitle(mScreenTitle);
        this.setSubtitle(mScreenSubtitle);
        this.setIcon(mScreenIcon);
        this.getPreferenceScreen().removeAll();

        showProgressBar();
        if (!TextUtils.isEmpty(mUriString)) {
            getSliceLiveData().observeForever(this);
        }
        if (TextUtils.isEmpty(mScreenTitle)) {
            mScreenTitle = getArguments().getCharSequence(SlicesConstants.TAG_SCREEN_TITLE, "");
        }
        super.onResume();
        if (!TextUtils.isEmpty(mUriString)) {
            getContext().getContentResolver().registerContentObserver(
                    SlicePreferencesUtil.getStatusPath(mUriString), false, mContentObserver);
        }
        fireFollowupPendingIntent();
    }

    private SliceLiveDataImpl getSliceLiveData() {
        return ContextSingleton.getInstance()
                .getSliceLiveData(getActivity(), Uri.parse(mUriString));
    }

    private void fireFollowupPendingIntent() {
        if (mFollowupPendingIntentExtras == null) {
            return;
        }
        // If there is followup pendingIntent returned from initial activity, send it.
        // Otherwise send the followup pendingIntent provided by slice api.
        Parcelable followupPendingIntent;
        try {
            followupPendingIntent = mFollowupPendingIntentExtrasCopy.getParcelableExtra(
                    EXTRA_SLICE_FOLLOWUP);
        } catch (Throwable ex) {
            // unable to parse, the Intent has custom Parcelable, fallback
            followupPendingIntent = null;
        }
        if (followupPendingIntent instanceof PendingIntent) {
            try {
                ((PendingIntent) followupPendingIntent).send();
            } catch (CanceledException e) {
                Log.e(TAG, "Followup PendingIntent for slice cannot be sent", e);
            }
        } else {
            if (mPreferenceFollowupIntent == null) {
                return;
            }
            try {
                mPreferenceFollowupIntent.send(getContext(),
                        mFollowupPendingIntentResultCode, mFollowupPendingIntentExtras);
            } catch (CanceledException e) {
                Log.e(TAG, "Followup PendingIntent for slice cannot be sent", e);
            }
            mPreferenceFollowupIntent = null;
        }
    }

    @Override
    public void onPause() {
        super.onPause();
        hideProgressBar();
        getContext().getContentResolver().unregisterContentObserver(mContentObserver);
        getSliceLiveData().removeObserver(this);
    }

    @Override
    public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
        PreferenceScreen preferenceScreen = getPreferenceManager()
                .createPreferenceScreen(getContext());
        setPreferenceScreen(preferenceScreen);

        TypedValue themeTypedValue = new TypedValue();
        getActivity().getTheme().resolveAttribute(R.attr.preferenceTheme, themeTypedValue, true);
        mContextThemeWrapper = new ContextThemeWrapper(getActivity(), themeTypedValue.resourceId);

    }

    private boolean isUriValid(String uri) {
        if (uri == null) {
            return false;
        }
        ContentProviderClient client =
                getContext().getContentResolver().acquireContentProviderClient(Uri.parse(uri));
        if (client != null) {
            client.close();
            return true;
        } else {
            return false;
        }
    }

    private void update() {
        mListContent = new ListContent(mSlice);
        PreferenceScreen preferenceScreen =
                getPreferenceManager().getPreferenceScreen();

        if (preferenceScreen == null) {
            return;
        }

        List<SliceContent> items = mListContent.getRowItems();
        if (items == null || items.size() == 0) {
            return;
        }

        SliceItem redirectSliceItem = SlicePreferencesUtil.getRedirectSlice(items);
        String redirectSlice = null;
        if (redirectSliceItem != null) {
            Data data = SlicePreferencesUtil.extract(redirectSliceItem);
            CharSequence title = SlicePreferencesUtil.getText(data.mTitleItem);
            if (!TextUtils.isEmpty(title)) {
                redirectSlice = title.toString();
            }
        }
        if (isUriValid(redirectSlice)) {
            getSliceLiveData().removeObserver(this);
            getContext().getContentResolver().unregisterContentObserver(mContentObserver);
            mUriString = redirectSlice;
            getSliceLiveData().observeForever(this);
            getContext().getContentResolver().registerContentObserver(
                    SlicePreferencesUtil.getStatusPath(mUriString), false, mContentObserver);
        }

        SliceItem screenTitleItem = SlicePreferencesUtil.getScreenTitleItem(items);
        if (screenTitleItem == null) {
            setTitle(mScreenTitle);
        } else {
            Data data = SlicePreferencesUtil.extract(screenTitleItem);
            mCurrentPageId = SlicePreferencesUtil.getPageId(screenTitleItem);
            CharSequence title = SlicePreferencesUtil.getText(data.mTitleItem);
            if (!TextUtils.isEmpty(title)) {
                setTitle(title);
                mScreenTitle = title;
            } else {
                setTitle(mScreenTitle);
            }

            CharSequence subtitle = SlicePreferencesUtil.getText(data.mSubtitleItem);
            setSubtitle(subtitle);

            Icon icon = SlicePreferencesUtil.getIcon(data.mStartItem);
            setIcon(icon);
        }

        SliceItem focusedPrefItem = SlicePreferencesUtil.getFocusedPreferenceItem(items);
        CharSequence defaultFocusedKey = null;
        if (focusedPrefItem != null) {
            Data data = SlicePreferencesUtil.extract(focusedPrefItem);
            CharSequence title = SlicePreferencesUtil.getText(data.mTitleItem);
            if (!TextUtils.isEmpty(title)) {
                defaultFocusedKey = title;
            }
        }

        List<Preference> newPrefs = new ArrayList<>();
        for (SliceContent contentItem : items) {
            SliceItem item = contentItem.getSliceItem();
            if (SlicesConstants.TYPE_PREFERENCE.equals(item.getSubType())
                    || SlicesConstants.TYPE_PREFERENCE_CATEGORY.equals(item.getSubType())
                    || SlicesConstants.TYPE_PREFERENCE_EMBEDDED_PLACEHOLDER.equals(
                            item.getSubType())) {
                Preference preference =
                        SlicePreferencesUtil.getPreference(
                                item, mContextThemeWrapper, getClass().getCanonicalName(),
                                getParentFragment() instanceof TwoPanelSettingsFragment);
                if (preference != null) {
                    newPrefs.add(preference);
                }
            }
        }
        updatePreferenceScreen(preferenceScreen, newPrefs);
        if (defaultFocusedKey != null) {
            scrollToPreference(defaultFocusedKey.toString());
        } else if (mLastFocusedPreferenceKey != null) {
            scrollToPreference(mLastFocusedPreferenceKey);
        }

        if (getParentFragment() instanceof TwoPanelSettingsFragment) {
            ((TwoPanelSettingsFragment) getParentFragment()).refocusPreference(this);
        }
        mIsMainPanelReady = true;

        resetA11yFocusIfNeeded();
    }

    // Because the SliceProvider may call for updates an uncertain amount of times, we
    // should have the current focus request a11yFocus after the update, since it will
    // be lost otherwise. The delay is to give the screen reader enough time to
    // process the update.
    private void resetA11yFocusIfNeeded() {
        if (isA11yOn()) {
            mHandler.postDelayed(() -> {
                if (isResumed() && getListView() != null && getListView().findFocus() != null) {
                    getListView().findFocus().requestAccessibilityFocus();
                }
            }, A11Y_FOCUS_REQUEST_DELAY);
        }
    }

    private boolean isA11yOn() {
        if (getActivity() == null) {
            return false;
        }
        return Settings.Secure.getInt(
                getActivity().getContentResolver(),
                Settings.Secure.ACCESSIBILITY_ENABLED, 0) == 1;
    }


    private void back() {
        if (getCallbackFragment() instanceof TwoPanelSettingsFragment) {
            TwoPanelSettingsFragment parentFragment =
                    (TwoPanelSettingsFragment) getCallbackFragment();
            if (parentFragment.isFragmentInTheMainPanel(this)) {
                parentFragment.navigateBack();
            }
        } else if (getCallbackFragment() instanceof OnePanelSliceFragmentContainer) {
            ((OnePanelSliceFragmentContainer) getCallbackFragment()).navigateBack();
        }
    }

    private void forward() {
        if (mIsMainPanelReady) {
            if (getCallbackFragment() instanceof TwoPanelSettingsFragment) {
                TwoPanelSettingsFragment parentFragment =
                        (TwoPanelSettingsFragment) getCallbackFragment();
                Preference chosenPreference = TwoPanelSettingsFragment.getChosenPreference(this);
                if (chosenPreference == null && mLastFocusedPreferenceKey != null) {
                    chosenPreference = findPreference(mLastFocusedPreferenceKey);
                }
                if (chosenPreference != null && chosenPreference instanceof HasSliceUri
                        && ((HasSliceUri) chosenPreference).getUri() != null) {
                    chosenPreference.setFragment(SliceFragment.class.getCanonicalName());
                    parentFragment.refocusPreferenceForceRefresh(chosenPreference, this);
                }
                if (parentFragment.isFragmentInTheMainPanel(this)) {
                    parentFragment.navigateToPreviewFragment();
                }
            }
        } else {
            mHandler.post(() -> forward());
        }
    }

    private void updatePreferenceScreen(PreferenceScreen screen, List<Preference> newPrefs) {
        // Remove all the preferences in the screen that satisfy such three cases:
        // (a) Preference without key
        // (b) Preference with key which does not appear in the new list.
        // (c) Preference with key which does appear in the new list, but the preference has changed
        // ability to handle slices and needs to be replaced instead of re-used.
        int index = 0;
        while (index < screen.getPreferenceCount()) {
            boolean needToRemoveCurrentPref = true;
            Preference oldPref = screen.getPreference(index);
            if (oldPref != null && oldPref.getKey() != null) {
                for (Preference newPref : newPrefs) {
                    if (newPref.getKey() != null && newPref.getKey().equals(oldPref.getKey())
                            && (newPref instanceof HasSliceUri)
                            == (oldPref instanceof HasSliceUri)) {
                        needToRemoveCurrentPref = false;
                        break;
                    }
                }
            }
            if (needToRemoveCurrentPref) {
                screen.removePreference(oldPref);
            } else {
                index++;
            }
        }

        Map<Integer, Boolean> twoStatePreferenceIsCheckedByOrder = new HashMap<>();
        for (int i = 0; i < newPrefs.size(); i++) {
            if (newPrefs.get(i) instanceof TwoStatePreference) {
                twoStatePreferenceIsCheckedByOrder.put(
                        i, ((TwoStatePreference) newPrefs.get(i)).isChecked());
            }
        }

        //Iterate the new preferences list and give each preference a correct order
        for (int i = 0; i < newPrefs.size(); i++) {
            Preference newPref = newPrefs.get(i);
            boolean neededToAddNewPref = true;
            // If the newPref has a key and has a corresponding old preference, update the old
            // preference and give it a new order.
            if (newPref.getKey() != null) {
                for (int j = 0; j < screen.getPreferenceCount(); j++) {
                    Preference oldPref = screen.getPreference(j);
                    // EmbeddedSlicePreference has its own slice observer
                    // (EmbeddedSlicePreferenceHelper). Should therefore not be updated by
                    // slice observer in SliceFragment.
                    boolean allowUpdate = !(oldPref instanceof EmbeddedSlicePreference);
                    boolean sameKey = oldPref.getKey() != null
                            && oldPref.getKey().equals(newPref.getKey());
                    if (allowUpdate && sameKey) {
                        oldPref.setIcon(newPref.getIcon());
                        oldPref.setTitle(newPref.getTitle());
                        oldPref.setSummary(newPref.getSummary());
                        oldPref.setEnabled(newPref.isEnabled());
                        oldPref.setSelectable(newPref.isSelectable());
                        oldPref.setFragment(newPref.getFragment());
                        oldPref.getExtras().putAll(newPref.getExtras());
                        if ((oldPref instanceof HasSliceAction)
                                && (newPref instanceof HasSliceAction)) {
                            ((HasSliceAction) oldPref)
                                    .setSliceAction(((HasSliceAction) newPref).getSliceAction());
                        }
                        if ((oldPref instanceof HasSliceUri)
                                && (newPref instanceof HasSliceUri)) {
                            ((HasSliceUri) oldPref)
                                    .setUri(((HasSliceUri) newPref).getUri());
                        }
                        if ((oldPref instanceof HasCustomContentDescription)
                                && (newPref instanceof HasCustomContentDescription)) {
                            ((HasCustomContentDescription) oldPref).setContentDescription(
                                    ((HasCustomContentDescription) newPref)
                                            .getContentDescription());
                        }
                        oldPref.setOrder(i);
                    }
                    if (sameKey) {
                        neededToAddNewPref = false;
                        break;
                    }
                }
            }
            // If the newPref cannot find a corresponding old preference, or it does not have a key,
            // add it to the screen with the correct order.
            if (neededToAddNewPref) {
                newPref.setOrder(i);
                screen.addPreference(newPref);
            }
        }
        //addPreference will reset the checked status of TwoStatePreference.
        //So we need to add them back
        for (int i = 0; i < screen.getPreferenceCount(); i++) {
            Preference screenPref = screen.getPreference(i);
            if (screenPref instanceof TwoStatePreference
                    && twoStatePreferenceIsCheckedByOrder.get(i) != null) {
                ((TwoStatePreference) screenPref)
                        .setChecked(twoStatePreferenceIsCheckedByOrder.get(i));
            }
        }
        removeAnimationClipping(getView());
    }

    protected void removeAnimationClipping(View v) {
        if (v instanceof ViewGroup) {
            ((ViewGroup) v).setClipChildren(false);
            ((ViewGroup) v).setClipToPadding(false);
            for (int index = 0; index < ((ViewGroup) v).getChildCount(); index++) {
                View child = ((ViewGroup) v).getChildAt(index);
                removeAnimationClipping(child);
            }
        }
    }

    @Override
    public void onPreferenceFocused(Preference preference) {
        setLastFocused(preference);
    }

    @Override
    public void onSeekbarPreferenceChanged(SliceSeekbarPreference preference, int addValue) {
        int curValue = preference.getValue();
        if((addValue > 0 && curValue < preference.getMax()) ||
           (addValue < 0 && curValue > preference.getMin())) {
            preference.setValue(curValue + addValue);

            try {
                Intent fillInIntent =
                        new Intent()
                                .putExtra(EXTRA_SLIDER_VALUE, preference.getValue())
                                .putExtra(EXTRA_PREFERENCE_KEY, preference.getKey());
                firePendingIntent((HasSliceAction) preference, fillInIntent);
            } catch (Exception e) {
                Log.e(TAG, "PendingIntent for slice cannot be sent", e);
            }
        }
    }

    @Override
    public boolean onPreferenceTreeClick(Preference preference) {
        if (preference instanceof SliceRadioPreference) {
            SliceRadioPreference radioPref = (SliceRadioPreference) preference;
            if (!radioPref.isChecked()) {
                radioPref.setChecked(true);
                if (TextUtils.isEmpty(radioPref.getUri())) {
                    return true;
                }
            }

            logEntrySelected(getPreferenceActionId(preference));
            Intent fillInIntent = new Intent().putExtra(EXTRA_PREFERENCE_KEY, preference.getKey());

            boolean result = firePendingIntent(radioPref, fillInIntent);
            radioPref.clearOtherRadioPreferences(getPreferenceScreen());
            if (result) {
                return true;
            }
        } else if (preference instanceof TwoStatePreference
                && preference instanceof HasSliceAction) {
            boolean isChecked = ((TwoStatePreference) preference).isChecked();
            preference.getExtras().putBoolean(EXTRA_PREFERENCE_INFO_STATUS, isChecked);
            if (getParentFragment() instanceof TwoPanelSettingsFragment) {
                ((TwoPanelSettingsFragment) getParentFragment()).refocusPreference(this);
            }
            logToggleInteracted(getPreferenceActionId(preference), isChecked);
            Intent fillInIntent =
                    new Intent()
                            .putExtra(EXTRA_TOGGLE_STATE, isChecked)
                            .putExtra(EXTRA_PREFERENCE_KEY, preference.getKey());
            if (firePendingIntent((HasSliceAction) preference, fillInIntent)) {
                return true;
            }
            return true;
        } else if (preference instanceof SlicePreference) {
            // In this case, we may intentionally ignore this entry selection to avoid double
            // logging as the action should result in a PAGE_FOCUSED event being logged.
            if (getPreferenceActionId(preference) != TvSettingsEnums.ENTRY_DEFAULT) {
                logEntrySelected(getPreferenceActionId(preference));
            }
            Intent fillInIntent =
                    new Intent().putExtra(EXTRA_PREFERENCE_KEY, preference.getKey());
            if (firePendingIntent((HasSliceAction) preference, fillInIntent)) {
                return true;
            }
        }

        return super.onPreferenceTreeClick(preference);
    }

    private boolean firePendingIntent(@NonNull HasSliceAction preference, Intent fillInIntent) {
        if (preference.getSliceAction() == null) {
            return false;
        }
        IntentSender intentSender = preference.getSliceAction().getAction().getIntentSender();
        mActivityResultLauncher.launch(
                new IntentSenderRequest.Builder(intentSender).setFillInIntent(
                        fillInIntent).build());
        if (preference.getFollowupSliceAction() != null) {
            mPreferenceFollowupIntent = preference.getFollowupSliceAction().getAction();
        }

        return true;
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putParcelable(KEY_PREFERENCE_FOLLOWUP_INTENT, mPreferenceFollowupIntent);
        outState.putInt(KEY_PREFERENCE_FOLLOWUP_RESULT_CODE, mFollowupPendingIntentResultCode);
        outState.putCharSequence(KEY_SCREEN_TITLE, mScreenTitle);
        outState.putCharSequence(KEY_SCREEN_SUBTITLE, mScreenSubtitle);
        outState.putParcelable(KEY_SCREEN_ICON, mScreenIcon);
        outState.putString(KEY_LAST_PREFERENCE, mLastFocusedPreferenceKey);
        outState.putString(KEY_URI_STRING, mUriString);
    }

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        if (savedInstanceState != null) {
            mPreferenceFollowupIntent =
                    savedInstanceState.getParcelable(KEY_PREFERENCE_FOLLOWUP_INTENT);
            mFollowupPendingIntentResultCode =
                    savedInstanceState.getInt(KEY_PREFERENCE_FOLLOWUP_RESULT_CODE);
            mScreenTitle = savedInstanceState.getCharSequence(KEY_SCREEN_TITLE);
            mScreenSubtitle = savedInstanceState.getCharSequence(KEY_SCREEN_SUBTITLE);
            mScreenIcon = savedInstanceState.getParcelable(KEY_SCREEN_ICON);
            mLastFocusedPreferenceKey = savedInstanceState.getString(KEY_LAST_PREFERENCE);
            mUriString = savedInstanceState.getString(KEY_URI_STRING);
        }
    }

    @Override
    public void onChanged(@NonNull Slice slice) {
        mSlice = slice;
        // Make TvSettings guard against the case that slice provider is not set up correctly
        if (slice == null || slice.getHints() == null) {
            return;
        }

        if (slice.getHints().contains(HINT_PARTIAL)) {
            showProgressBar();
        } else {
            hideProgressBar();
        }
        mIsMainPanelReady = false;
        update();
    }

    private void showProgressBar() {
        View view = this.getView();
        View progressBar = view == null ? null : getView().findViewById(R.id.progress_bar);
        if (progressBar != null) {
            progressBar.bringToFront();
            progressBar.setVisibility(View.VISIBLE);
        }
    }

    private void hideProgressBar() {
        View view = this.getView();
        View progressBar = view == null ? null : getView().findViewById(R.id.progress_bar);
        if (progressBar != null) {
            progressBar.setVisibility(View.GONE);
        }
    }

    private void setSubtitle(CharSequence subtitle) {
        View view = this.getView();
        TextView decorSubtitle = view == null
                ? null
                : (TextView) view.findViewById(R.id.decor_subtitle);
        if (decorSubtitle != null) {
            // This is to remedy some complicated RTL scenario such as Hebrew RTL Account slice with
            // English account name subtitle.
            if (getResources().getConfiguration().getLayoutDirection()
                    == View.LAYOUT_DIRECTION_RTL) {
                decorSubtitle.setGravity(Gravity.TOP | Gravity.RIGHT);
            }
            if (TextUtils.isEmpty(subtitle)) {
                decorSubtitle.setVisibility(View.GONE);
            } else {
                decorSubtitle.setVisibility(View.VISIBLE);
                decorSubtitle.setText(subtitle);
            }
        }
        mScreenSubtitle = subtitle;
    }

    private void setIcon(Icon icon) {
        View view = this.getView();
        ImageView decorIcon = view == null ? null : (ImageView) view.findViewById(R.id.decor_icon);
        if (decorIcon != null && icon != null) {
            TextView decorTitle = view.findViewById(R.id.decor_title);
            if (decorTitle != null) {
                decorTitle.setMaxWidth(
                        getResources().getDimensionPixelSize(R.dimen.decor_title_width));
            }
            decorIcon.setImageDrawable(icon.loadDrawable(mContextThemeWrapper));
            decorIcon.setVisibility(View.VISIBLE);
        } else if (decorIcon != null) {
            decorIcon.setVisibility(View.GONE);
        }
        mScreenIcon = icon;
    }

    @Override
    public View onCreateView(
            LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        final ViewGroup view =
                (ViewGroup) super.onCreateView(inflater, container, savedInstanceState);
        LayoutInflater themedInflater = LayoutInflater.from(view.getContext());
        final View newTitleContainer = themedInflater.inflate(
                R.layout.slice_title_container, container, false);
        view.removeView(view.findViewById(R.id.decor_title_container));
        view.addView(newTitleContainer, 0);

        if (newTitleContainer != null) {
            newTitleContainer.setOutlineProvider(null);
            newTitleContainer.setBackgroundResource(R.color.tp_preference_panel_background_color);
        }

        final View newContainer =
                themedInflater.inflate(R.layout.slice_progress_bar, container, false);
        if (newContainer != null) {
            ((ViewGroup) newContainer).addView(view);
        }
        return newContainer;
    }

    public void setLastFocused(Preference preference) {
        mLastFocusedPreferenceKey = preference.getKey();
    }

    private void handleUri(Uri uri) {
        String uriString = uri.getQueryParameter(SlicesConstants.PARAMETER_URI);
        String errorMessage = uri.getQueryParameter(SlicesConstants.PARAMETER_ERROR);
        // Display the errorMessage based upon two different scenarios:
        // a) If the provided uri string matches with current page slice uri(usually happens
        // when the data fails to correctly load), show the errors in the current panel using
        // InfoFragment UI.
        // b) If the provided uri string does not match with current page slice uri(usually happens
        // when the data fails to save), show the error message as the toast.
        if (uriString != null && errorMessage != null) {
            if (!uriString.equals(mUriString)) {
                showErrorMessageAsToast(errorMessage);
            } else {
                showErrorMessage(errorMessage);
            }
        }
        // Provider should provide the correct slice uri in the parameter if it wants to do certain
        // action(includes go back, forward), otherwise TvSettings would ignore it.
        if (uriString == null || !uriString.equals(mUriString)) {
            return;
        }
        String direction = uri.getQueryParameter(SlicesConstants.PARAMETER_DIRECTION);
        if (direction != null) {
            if (direction.equals(SlicesConstants.FORWARD)) {
                forward();
            } else if (direction.equals(SlicesConstants.BACKWARD)) {
                back();
            } else if (direction.equals(SlicesConstants.EXIT)) {
                finish();
            }
        }
    }

    private void showErrorMessageAsToast(String errorMessage) {
        Toast.makeText(getActivity(), errorMessage, Toast.LENGTH_SHORT).show();
    }

    private void finish() {
        getActivity().setResult(Activity.RESULT_OK);
        getActivity().finish();
    }

    private void showErrorMessage(String errorMessage) {
        if (getCallbackFragment() instanceof TwoPanelSettingsFragment) {
            ((TwoPanelSettingsFragment) getCallbackFragment()).showErrorMessage(errorMessage, this);
        }
    }

    private int getPreferenceActionId(Preference preference) {
        if (preference instanceof HasSliceAction) {
            return ((HasSliceAction) preference).getActionId() != 0
                    ? ((HasSliceAction) preference).getActionId()
                    : TvSettingsEnums.ENTRY_DEFAULT;
        }
        return TvSettingsEnums.ENTRY_DEFAULT;
    }

    public CharSequence getScreenTitle() {
        return mScreenTitle;
    }

    @Override
    protected int getPageId() {
        return mCurrentPageId != 0 ? mCurrentPageId : TvSettingsEnums.PAGE_SLICE_DEFAULT;
    }

    @Deprecated
    public int getMetricsCategory() {
        return 0;
    }
}
