1ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav/*
2ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav * Copyright (C) 2015 The Android Open Source Project
3ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav *
4ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav * Licensed under the Apache License, Version 2.0 (the "License");
5ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav * you may not use this file except in compliance with the License.
6ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav * You may obtain a copy of the License at
7ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav *
8ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav *      http://www.apache.org/licenses/LICENSE-2.0
9ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav *
10ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav * Unless required by applicable law or agreed to in writing, software
11ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav * distributed under the License is distributed on an "AS IS" BASIS,
12ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav * See the License for the specific language governing permissions and
14ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav * limitations under the License.
15ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav */
16ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav
17ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslavpackage com.android.packageinstaller.permission.ui.television;
18ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav
19ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslavimport android.annotation.Nullable;
20ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslavimport android.os.Bundle;
21ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslavimport android.support.v14.preference.PreferenceFragment;
22ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslavimport android.support.v17.leanback.widget.VerticalGridView;
23ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslavimport android.support.v7.preference.PreferenceScreen;
24ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslavimport android.support.v7.widget.RecyclerView;
25ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslavimport android.view.LayoutInflater;
26ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslavimport android.view.View;
27ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslavimport android.view.ViewGroup;
28ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslavimport android.view.animation.Animation;
29ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslavimport android.view.animation.Animation.AnimationListener;
30ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslavimport android.view.animation.AnimationUtils;
31ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslavimport android.widget.TextView;
3209370123353d8b925e644c05a9cf7434927e3ac9Svetoslavimport com.android.packageinstaller.DeviceUtils;
33ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslavimport com.android.packageinstaller.R;
34ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav
35ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslavpublic abstract class PermissionsFrameFragment extends PreferenceFragment {
36ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav
37b22473b280b2b893167ffaae24e9b1c43d343a3fKeyvan Amiri    // Key identifying the preference used on TV as the extra header in a permission fragment.
38b22473b280b2b893167ffaae24e9b1c43d343a3fKeyvan Amiri    // This is to distinguish it from the rest of the preferences
39b22473b280b2b893167ffaae24e9b1c43d343a3fKeyvan Amiri    protected static final String HEADER_PREFERENCE_KEY = "HeaderPreferenceKey";
40ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav
41ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav    private ViewGroup mPreferencesContainer;
42ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav
43b22473b280b2b893167ffaae24e9b1c43d343a3fKeyvan Amiri    // TV-specific instance variable
44b22473b280b2b893167ffaae24e9b1c43d343a3fKeyvan Amiri    @Nullable private RecyclerView mGridView;
45ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav
46ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav    private View mLoadingView;
47ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav    private ViewGroup mPrefsView;
48ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav    private boolean mIsLoading;
49ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav
50ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav    /**
51ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav     * Returns the view group that holds the preferences objects. This will
52ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav     * only be set after {@link #onCreateView} has been called.
53ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav     */
54ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav    protected final ViewGroup getPreferencesContainer() {
55ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav        return mPreferencesContainer;
56ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav    }
57ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav
58ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav    @Override
59ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav    public View onCreateView(LayoutInflater inflater, ViewGroup container,
60ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav            Bundle savedInstanceState) {
61ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav        ViewGroup rootView = (ViewGroup) inflater.inflate(R.layout.permissions_frame, container,
62ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav                        false);
63ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav        mPrefsView = (ViewGroup) rootView.findViewById(R.id.prefs_container);
64ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav        if (mPrefsView == null) {
65ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav            mPrefsView = rootView;
66ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav        }
67ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav        mLoadingView = rootView.findViewById(R.id.loading_container);
68ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav        mPreferencesContainer = (ViewGroup) super.onCreateView(
69ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav                inflater, mPrefsView, savedInstanceState);
70ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav        setLoading(mIsLoading, false, true /* force */);
71ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav        mPrefsView.addView(mPreferencesContainer);
72ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav        return rootView;
73ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav    }
74ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav
75ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav    @Override
76f9bf4f795615bac03d1b35a0138318473b1ef6bfSvet Ganov    public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
77ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav        PreferenceScreen preferences = getPreferenceScreen();
78ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav        if (preferences == null) {
79ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav            preferences = getPreferenceManager().createPreferenceScreen(getActivity());
80ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav            setPreferenceScreen(preferences);
81ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav        }
82ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav    }
83ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav
84ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav    protected void setLoading(boolean loading, boolean animate) {
85ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav        setLoading(loading, animate, false);
86ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav    }
87ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav
88ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav    private void setLoading(boolean loading, boolean animate, boolean force) {
89ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav        if (mIsLoading != loading || force) {
90ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav            mIsLoading = loading;
91ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav            if (getView() == null) {
92ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav                // If there is no created view, there is no reason to animate.
93ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav                animate = false;
94ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav            }
95ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav            if (mPrefsView != null) {
96ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav                setViewShown(mPrefsView, !loading, animate);
97ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav            }
98ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav            if (mLoadingView != null) {
99ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav                setViewShown(mLoadingView, loading, animate);
100ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav            }
101ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav        }
102ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav    }
103ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav
104ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav    private void setViewShown(final View view, boolean shown, boolean animate) {
105ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav        if (animate) {
106ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav            Animation animation = AnimationUtils.loadAnimation(getContext(),
107ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav                    shown ? android.R.anim.fade_in : android.R.anim.fade_out);
108ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav            if (shown) {
109ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav                view.setVisibility(View.VISIBLE);
110ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav            } else {
111ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav                animation.setAnimationListener(new AnimationListener() {
112ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav                    @Override
113ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav                    public void onAnimationStart(Animation animation) {
114ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav                    }
115ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav
116ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav                    @Override
117ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav                    public void onAnimationRepeat(Animation animation) {
118ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav                    }
119ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav
120ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav                    @Override
121ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav                    public void onAnimationEnd(Animation animation) {
122ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav                        view.setVisibility(View.INVISIBLE);
123ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav                    }
124ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav                });
125ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav            }
126ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav            view.startAnimation(animation);
127ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav        } else {
128ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav            view.clearAnimation();
129ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav            view.setVisibility(shown ? View.VISIBLE : View.INVISIBLE);
130ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav        }
131ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav    }
132ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav
133ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav    @Override
134ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav    public RecyclerView onCreateRecyclerView(LayoutInflater inflater, ViewGroup parent,
135b22473b280b2b893167ffaae24e9b1c43d343a3fKeyvan Amiri                                             Bundle savedInstanceState) {
136b22473b280b2b893167ffaae24e9b1c43d343a3fKeyvan Amiri        VerticalGridView verticalGridView = (VerticalGridView) inflater.inflate(
137b22473b280b2b893167ffaae24e9b1c43d343a3fKeyvan Amiri                R.layout.leanback_preferences_list, parent, false);
138b22473b280b2b893167ffaae24e9b1c43d343a3fKeyvan Amiri        verticalGridView.setWindowAlignment(VerticalGridView.WINDOW_ALIGN_BOTH_EDGE);
139b22473b280b2b893167ffaae24e9b1c43d343a3fKeyvan Amiri        verticalGridView.setFocusScrollStrategy(VerticalGridView.FOCUS_SCROLL_ALIGNED);
140b22473b280b2b893167ffaae24e9b1c43d343a3fKeyvan Amiri        mGridView = verticalGridView;
141b22473b280b2b893167ffaae24e9b1c43d343a3fKeyvan Amiri        return mGridView;
142ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav    }
143ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav
144ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav    @Override
145ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav    protected RecyclerView.Adapter<?> onCreateAdapter(PreferenceScreen preferenceScreen) {
146ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav        final RecyclerView.Adapter<?> adapter = super.onCreateAdapter(preferenceScreen);
147ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav
148ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav        if (adapter != null) {
149ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav            final TextView emptyView = (TextView) getView().findViewById(R.id.no_permissions);
150ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav            onSetEmptyText(emptyView);
151ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav            final RecyclerView recyclerView = getListView();
152b22473b280b2b893167ffaae24e9b1c43d343a3fKeyvan Amiri            adapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() {
153ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav                @Override
154ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav                public void onChanged() {
155ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav                    checkEmpty();
156ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav                }
157ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav
158ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav                @Override
159ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav                public void onItemRangeInserted(int positionStart, int itemCount) {
160ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav                    checkEmpty();
161ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav                }
162ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav
163ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav                @Override
164ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav                public void onItemRangeRemoved(int positionStart, int itemCount) {
165ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav                    checkEmpty();
166ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav                }
167ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav
168ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav                private void checkEmpty() {
169b22473b280b2b893167ffaae24e9b1c43d343a3fKeyvan Amiri                    boolean isEmpty = isPreferenceListEmpty();
170ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav                    emptyView.setVisibility(isEmpty ? View.VISIBLE : View.GONE);
171b22473b280b2b893167ffaae24e9b1c43d343a3fKeyvan Amiri                    recyclerView.setVisibility(isEmpty && adapter.getItemCount() == 0 ?
172b22473b280b2b893167ffaae24e9b1c43d343a3fKeyvan Amiri                            View.GONE : View.VISIBLE);
173ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav                    if (!isEmpty && mGridView != null) {
174ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav                        mGridView.requestFocus();
175ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav                    }
176ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav                }
177ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav            });
178ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav
179b22473b280b2b893167ffaae24e9b1c43d343a3fKeyvan Amiri            boolean isEmpty = isPreferenceListEmpty();
180ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav            emptyView.setVisibility(isEmpty ? View.VISIBLE : View.GONE);
181b22473b280b2b893167ffaae24e9b1c43d343a3fKeyvan Amiri            recyclerView.setVisibility(isEmpty && adapter.getItemCount() == 0 ?
182b22473b280b2b893167ffaae24e9b1c43d343a3fKeyvan Amiri                    View.GONE : View.VISIBLE);
183ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav            if (!isEmpty && mGridView != null) {
184ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav                mGridView.requestFocus();
185ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav            }
186ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav        }
187ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav
188ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav        return adapter;
189ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav    }
190ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav
191b22473b280b2b893167ffaae24e9b1c43d343a3fKeyvan Amiri    private boolean isPreferenceListEmpty() {
192b22473b280b2b893167ffaae24e9b1c43d343a3fKeyvan Amiri        PreferenceScreen screen = getPreferenceScreen();
193b22473b280b2b893167ffaae24e9b1c43d343a3fKeyvan Amiri        return screen.getPreferenceCount() == 0 || (
194b22473b280b2b893167ffaae24e9b1c43d343a3fKeyvan Amiri                screen.getPreferenceCount() == 1 &&
195b22473b280b2b893167ffaae24e9b1c43d343a3fKeyvan Amiri                        (screen.findPreference(HEADER_PREFERENCE_KEY) != null));
196b22473b280b2b893167ffaae24e9b1c43d343a3fKeyvan Amiri    }
197b22473b280b2b893167ffaae24e9b1c43d343a3fKeyvan Amiri
198ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav    /**
199ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav     * Hook for subclasses to change the default text of the empty view.
200ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav     * Base implementation leaves the default empty view text.
201ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav     *
202ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav     * @param textView the empty text view
203ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav     */
204ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav    protected void onSetEmptyText(TextView textView) {
205ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav    }
206ef861375eebd9ac6cce7c0bb163380ab1c951063Svetoslav}
207