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.handheld;
18 
19 import android.app.ActionBar;
20 import android.os.Bundle;
21 import android.view.LayoutInflater;
22 import android.view.Menu;
23 import android.view.MenuInflater;
24 import android.view.View;
25 import android.view.ViewGroup;
26 import android.view.animation.Animation;
27 import android.view.animation.Animation.AnimationListener;
28 import android.view.animation.AnimationUtils;
29 import android.widget.TextView;
30 
31 import androidx.preference.PreferenceFragmentCompat;
32 import androidx.recyclerview.widget.RecyclerView;
33 
34 import com.android.modules.utils.build.SdkLevel;
35 import com.android.permissioncontroller.R;
36 import com.android.permissioncontroller.permission.utils.Utils;
37 import com.android.settingslib.widget.ActionBarShadowController;
38 
39 public abstract class PermissionsFrameFragment extends PreferenceFragmentCompat {
40     private static final String LOG_TAG = PermissionsFrameFragment.class.getSimpleName();
41 
42     static final int MENU_ALL_PERMS = Menu.FIRST + 1;
43     public static final int MENU_SHOW_SYSTEM = Menu.FIRST + 2;
44     public static final int MENU_HIDE_SYSTEM = Menu.FIRST + 3;
45 
46     private ViewGroup mPreferencesContainer;
47 
48     private TextView mEmptyView;
49     private View mLoadingView;
50     private View mProgressHeader;
51     private View mProgressView;
52     private ViewGroup mPrefsView;
53     private boolean mIsLoading;
54 
55     protected boolean mUseShadowController = true;
56 
57     /**
58      * Returns the view group that holds the preferences objects. This will
59      * only be set after {@link #onCreateView} has been called.
60      */
getPreferencesContainer()61     protected final ViewGroup getPreferencesContainer() {
62         return mPreferencesContainer;
63     }
64 
65     @Override
onCreateOptionsMenu(Menu menu, MenuInflater inflater)66     public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
67         super.onCreateOptionsMenu(menu, inflater);
68 
69         if (!SdkLevel.isAtLeastS()) {
70             Utils.prepareSearchMenuItem(menu, requireContext());
71         }
72     }
73 
74     @Override
onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)75     public View onCreateView(LayoutInflater inflater, ViewGroup container,
76             Bundle savedInstanceState) {
77         ViewGroup rootView = (ViewGroup) inflater.inflate(R.layout.permissions_frame, container,
78                         false);
79         mPrefsView = (ViewGroup) rootView.findViewById(R.id.prefs_container);
80         if (mPrefsView == null) {
81             mPrefsView = rootView;
82         }
83         mEmptyView = mPrefsView.findViewById(R.id.no_permissions);
84         mEmptyView.setText(getEmptyViewString());
85         mLoadingView = rootView.findViewById(R.id.loading_container);
86         mPreferencesContainer = (ViewGroup) super.onCreateView(
87                 inflater, mPrefsView, savedInstanceState);
88         setLoading(mIsLoading, false, true /* force */);
89         mPrefsView.addView(mPreferencesContainer);
90         mProgressHeader = rootView.requireViewById(
91                 com.android.settingslib.widget.progressbar.R.id.progress_bar_animation);
92         mProgressView = rootView.requireViewById(
93                 com.android.settingslib.widget.progressbar.R.id.progress_bar_background);
94         setProgressBarVisible(false);
95         getListView().setFocusable(false);
96         return rootView;
97     }
98 
99     @Override
onStart()100     public void onStart() {
101         super.onStart();
102 
103         if (getListView() != null) {
104             ActionBar ab = getActivity().getActionBar();
105             if (ab != null) {
106                 ab.setElevation(0);
107             }
108             if (mUseShadowController) {
109                 ActionBarShadowController.attachToView(getActivity(), getLifecycle(),
110                         getListView());
111             }
112         }
113     }
114 
115     @Override
onCreatePreferences(Bundle bundle, String s)116     public void onCreatePreferences(Bundle bundle, String s) {
117         // empty
118     }
119 
setLoading(boolean loading, boolean animate)120     protected void setLoading(boolean loading, boolean animate) {
121         setLoading(loading, animate, false);
122     }
123 
setLoading(boolean loading, boolean animate, boolean force)124     private void setLoading(boolean loading, boolean animate, boolean force) {
125         if (mIsLoading != loading || force) {
126             mIsLoading = loading;
127             if (getView() == null) {
128                 // If there is no created view, there is no reason to animate.
129                 animate = false;
130             }
131             if (mPrefsView != null) {
132                 setViewShown(mPrefsView, !loading, animate);
133             }
134             if (mLoadingView != null) {
135                 setViewShown(mLoadingView, loading, animate);
136             }
137         }
138     }
139 
setProgressBarVisible(boolean visible)140     protected void setProgressBarVisible(boolean visible) {
141         mProgressHeader.setVisibility(visible ? View.VISIBLE : View.GONE);
142         mProgressView.setVisibility(visible ? View.VISIBLE : View.GONE);
143     }
144 
145     /**
146      * Either show the empty view or the recycler view. To be called any time the adapter changes.
147      */
updateEmptyState()148     void updateEmptyState() {
149         RecyclerView prefs = getListView();
150 
151         // This might be called before onCreateView, hence emptyView and prefs can be null
152         if (mEmptyView != null && prefs != null) {
153             if (prefs.getAdapter() != null && prefs.getAdapter().getItemCount() != 0) {
154                 mEmptyView.setVisibility(View.GONE);
155                 prefs.setVisibility(View.VISIBLE);
156             } else {
157                 mEmptyView.setVisibility(View.VISIBLE);
158                 prefs.setVisibility(View.GONE);
159             }
160         }
161     }
162 
163     @Override
onBindPreferences()164     protected void onBindPreferences() {
165         super.onBindPreferences();
166 
167         RecyclerView.Adapter adapter = getListView().getAdapter();
168 
169         if (adapter != null) {
170             adapter.registerAdapterDataObserver(
171                     new RecyclerView.AdapterDataObserver() {
172                         @Override
173                         public void onChanged() {
174                             updateEmptyState();
175                         }
176 
177                         @Override
178                         public void onItemRangeInserted(int positionStart, int itemCount) {
179                             updateEmptyState();
180                         }
181 
182                         @Override
183                         public void onItemRangeRemoved(int positionStart, int itemCount) {
184                             updateEmptyState();
185                         }
186                     });
187         }
188 
189         updateEmptyState();
190     }
191 
setViewShown(final View view, boolean shown, boolean animate)192     private void setViewShown(final View view, boolean shown, boolean animate) {
193         // Clear out previous animation listeners.
194         if (view.getAnimation() != null) {
195             view.getAnimation().setAnimationListener(null);
196         }
197         view.clearAnimation();
198         if (animate) {
199             Animation animation = AnimationUtils.loadAnimation(getContext(),
200                     shown ? android.R.anim.fade_in : android.R.anim.fade_out);
201             if (shown) {
202                 view.setVisibility(View.VISIBLE);
203             } else {
204                 animation.setAnimationListener(new AnimationListener() {
205                     @Override
206                     public void onAnimationStart(Animation animation) {
207                     }
208 
209                     @Override
210                     public void onAnimationRepeat(Animation animation) {
211                     }
212 
213                     @Override
214                     public void onAnimationEnd(Animation animation) {
215                         view.setVisibility(View.INVISIBLE);
216                     }
217                 });
218             }
219             view.startAnimation(animation);
220         } else {
221             view.setVisibility(shown ? View.VISIBLE : View.INVISIBLE);
222         }
223     }
224 
225     /**
226      * @return the id of the string to display when there are no entries to show.
227      */
getEmptyViewString()228     public int getEmptyViewString() {
229         return R.string.no_permissions;
230     }
231 }
232