1 /*
2  * Copyright (C) 2015 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.permissioncontroller.permission.ui.television;
18 
19 import android.os.Bundle;
20 import android.view.LayoutInflater;
21 import android.view.View;
22 import android.view.ViewGroup;
23 import android.view.animation.Animation;
24 import android.view.animation.Animation.AnimationListener;
25 import android.view.animation.AnimationUtils;
26 import android.widget.TextView;
27 
28 import androidx.annotation.Nullable;
29 import androidx.leanback.widget.VerticalGridView;
30 import androidx.preference.PreferenceFragmentCompat;
31 import androidx.preference.PreferenceScreen;
32 import androidx.recyclerview.widget.RecyclerView;
33 
34 import com.android.permissioncontroller.R;
35 
36 public abstract class PermissionsFrameFragment extends PreferenceFragmentCompat {
37 
38     // Key identifying the preference used on TV as the extra header in a permission fragment.
39     // This is to distinguish it from the rest of the preferences
40     protected static final String HEADER_PREFERENCE_KEY = "HeaderPreferenceKey";
41 
42     private ViewGroup mPreferencesContainer;
43 
44     // TV-specific instance variable
45     @Nullable private RecyclerView mGridView;
46 
47     private View mLoadingView;
48     private ViewGroup mPrefsView;
49     private boolean mIsLoading;
50     private TextView mEmptyView;
51 
52     /**
53      * Returns the view group that holds the preferences objects. This will
54      * only be set after {@link #onCreateView} has been called.
55      */
getPreferencesContainer()56     protected final ViewGroup getPreferencesContainer() {
57         return mPreferencesContainer;
58     }
59 
60     @Override
onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)61     public View onCreateView(LayoutInflater inflater, ViewGroup container,
62             Bundle savedInstanceState) {
63         ViewGroup rootView = (ViewGroup) inflater.inflate(R.layout.permissions_frame, container,
64                         false);
65         mPrefsView = (ViewGroup) rootView.findViewById(R.id.prefs_container);
66         if (mPrefsView == null) {
67             mPrefsView = rootView;
68         }
69         mLoadingView = rootView.findViewById(R.id.loading_container);
70         mEmptyView = (TextView) rootView.findViewById(R.id.no_permissions);
71         mPreferencesContainer = (ViewGroup) super.onCreateView(
72                 inflater, mPrefsView, savedInstanceState);
73         setLoading(mIsLoading, false, true /* force */);
74         mPrefsView.addView(mPreferencesContainer);
75         return rootView;
76     }
77 
78     @Override
onCreatePreferences(Bundle savedInstanceState, String rootKey)79     public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
80         PreferenceScreen preferences = getPreferenceScreen();
81         if (preferences == null) {
82             preferences = getPreferenceManager().createPreferenceScreen(getActivity());
83             setPreferenceScreen(preferences);
84         }
85     }
86 
setLoading(boolean loading, boolean animate)87     protected void setLoading(boolean loading, boolean animate) {
88         setLoading(loading, animate, false);
89     }
90 
setLoading(boolean loading, boolean animate, boolean force)91     private void setLoading(boolean loading, boolean animate, boolean force) {
92         if (mIsLoading != loading || force) {
93             mIsLoading = loading;
94             if (getView() == null) {
95                 // If there is no created view, there is no reason to animate.
96                 animate = false;
97             }
98             if (mPrefsView != null) {
99                 setViewShown(mPrefsView, !loading, animate);
100             }
101             if (mLoadingView != null) {
102                 setViewShown(mLoadingView, loading, animate);
103             }
104             if (!mIsLoading) {
105                 checkEmpty();
106             }
107         }
108     }
109 
setViewShown(final View view, boolean shown, boolean animate)110     private void setViewShown(final View view, boolean shown, boolean animate) {
111         if (animate) {
112             Animation animation = AnimationUtils.loadAnimation(getContext(),
113                     shown ? android.R.anim.fade_in : android.R.anim.fade_out);
114             if (shown) {
115                 view.setVisibility(View.VISIBLE);
116             } else {
117                 animation.setAnimationListener(new AnimationListener() {
118                     @Override
119                     public void onAnimationStart(Animation animation) {
120                     }
121 
122                     @Override
123                     public void onAnimationRepeat(Animation animation) {
124                     }
125 
126                     @Override
127                     public void onAnimationEnd(Animation animation) {
128                         view.setVisibility(View.INVISIBLE);
129                     }
130                 });
131             }
132             view.startAnimation(animation);
133         } else {
134             view.clearAnimation();
135             view.setVisibility(shown ? View.VISIBLE : View.INVISIBLE);
136         }
137     }
138 
139     @Override
onCreateRecyclerView(LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState)140     public RecyclerView onCreateRecyclerView(LayoutInflater inflater, ViewGroup parent,
141                                              Bundle savedInstanceState) {
142         VerticalGridView verticalGridView = (VerticalGridView) inflater.inflate(
143                 androidx.leanback.preference.R.layout.leanback_preferences_list, parent, false);
144         verticalGridView.setWindowAlignment(VerticalGridView.WINDOW_ALIGN_BOTH_EDGE);
145         verticalGridView.setFocusScrollStrategy(VerticalGridView.FOCUS_SCROLL_ALIGNED);
146         mGridView = verticalGridView;
147         return mGridView;
148     }
149 
150     @Override
onCreateAdapter(PreferenceScreen preferenceScreen)151     protected RecyclerView.Adapter<?> onCreateAdapter(PreferenceScreen preferenceScreen) {
152         final RecyclerView.Adapter<?> adapter = super.onCreateAdapter(preferenceScreen);
153 
154         if (adapter != null) {
155             onSetEmptyText(mEmptyView);
156             adapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() {
157                 @Override
158                 public void onChanged() {
159                     checkEmpty();
160                 }
161 
162                 @Override
163                 public void onItemRangeInserted(int positionStart, int itemCount) {
164                     checkEmpty();
165                 }
166 
167                 @Override
168                 public void onItemRangeRemoved(int positionStart, int itemCount) {
169                     checkEmpty();
170                 }
171             });
172 
173             checkEmpty();
174         }
175 
176         return adapter;
177     }
178 
checkEmpty()179     private void checkEmpty() {
180         if (mIsLoading || getListView() == null || getListView().getAdapter() == null) return;
181 
182         boolean isEmpty = isPreferenceListEmpty();
183         mEmptyView.setVisibility(isEmpty ? View.VISIBLE : View.GONE);
184         getListView().setVisibility(isEmpty && getListView().getAdapter().getItemCount() == 0
185                 ? View.GONE : View.VISIBLE);
186         if (!isEmpty && mGridView != null) {
187             mGridView.requestFocus();
188         }
189     }
190 
isPreferenceListEmpty()191     private boolean isPreferenceListEmpty() {
192         PreferenceScreen screen = getPreferenceScreen();
193         return screen.getPreferenceCount() == 0 || (
194                 screen.getPreferenceCount() == 1 &&
195                         (screen.findPreference(HEADER_PREFERENCE_KEY) != null));
196     }
197 
198     /**
199      * Hook for subclasses to change the default text of the empty view.
200      * Base implementation leaves the default empty view text.
201      *
202      * @param textView the empty text view
203      */
onSetEmptyText(TextView textView)204     protected void onSetEmptyText(TextView textView) {
205     }
206 }
207