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(R.id.progress_bar_animation); 91 mProgressView = rootView.requireViewById(R.id.progress_bar_background); 92 setProgressBarVisible(false); 93 getListView().setFocusable(false); 94 return rootView; 95 } 96 97 @Override onStart()98 public void onStart() { 99 super.onStart(); 100 101 if (getListView() != null) { 102 ActionBar ab = getActivity().getActionBar(); 103 if (ab != null) { 104 ab.setElevation(0); 105 } 106 if (mUseShadowController) { 107 ActionBarShadowController.attachToView(getActivity(), getLifecycle(), 108 getListView()); 109 } 110 } 111 } 112 113 @Override onCreatePreferences(Bundle bundle, String s)114 public void onCreatePreferences(Bundle bundle, String s) { 115 // empty 116 } 117 setLoading(boolean loading, boolean animate)118 protected void setLoading(boolean loading, boolean animate) { 119 setLoading(loading, animate, false); 120 } 121 setLoading(boolean loading, boolean animate, boolean force)122 private void setLoading(boolean loading, boolean animate, boolean force) { 123 if (mIsLoading != loading || force) { 124 mIsLoading = loading; 125 if (getView() == null) { 126 // If there is no created view, there is no reason to animate. 127 animate = false; 128 } 129 if (mPrefsView != null) { 130 setViewShown(mPrefsView, !loading, animate); 131 } 132 if (mLoadingView != null) { 133 setViewShown(mLoadingView, loading, animate); 134 } 135 } 136 } 137 setProgressBarVisible(boolean visible)138 protected void setProgressBarVisible(boolean visible) { 139 mProgressHeader.setVisibility(visible ? View.VISIBLE : View.GONE); 140 mProgressView.setVisibility(visible ? View.VISIBLE : View.GONE); 141 } 142 143 /** 144 * Either show the empty view or the recycler view. To be called any time the adapter changes. 145 */ updateEmptyState()146 void updateEmptyState() { 147 RecyclerView prefs = getListView(); 148 149 // This might be called before onCreateView, hence emptyView and prefs can be null 150 if (mEmptyView != null && prefs != null) { 151 if (prefs.getAdapter() != null && prefs.getAdapter().getItemCount() != 0) { 152 mEmptyView.setVisibility(View.GONE); 153 prefs.setVisibility(View.VISIBLE); 154 } else { 155 mEmptyView.setVisibility(View.VISIBLE); 156 prefs.setVisibility(View.GONE); 157 } 158 } 159 } 160 161 @Override onBindPreferences()162 protected void onBindPreferences() { 163 super.onBindPreferences(); 164 165 RecyclerView.Adapter adapter = getListView().getAdapter(); 166 167 if (adapter != null) { 168 adapter.registerAdapterDataObserver( 169 new RecyclerView.AdapterDataObserver() { 170 @Override 171 public void onChanged() { 172 updateEmptyState(); 173 } 174 175 @Override 176 public void onItemRangeInserted(int positionStart, int itemCount) { 177 updateEmptyState(); 178 } 179 180 @Override 181 public void onItemRangeRemoved(int positionStart, int itemCount) { 182 updateEmptyState(); 183 } 184 }); 185 } 186 187 updateEmptyState(); 188 } 189 setViewShown(final View view, boolean shown, boolean animate)190 private void setViewShown(final View view, boolean shown, boolean animate) { 191 // Clear out previous animation listeners. 192 if (view.getAnimation() != null) { 193 view.getAnimation().setAnimationListener(null); 194 } 195 view.clearAnimation(); 196 if (animate) { 197 Animation animation = AnimationUtils.loadAnimation(getContext(), 198 shown ? android.R.anim.fade_in : android.R.anim.fade_out); 199 if (shown) { 200 view.setVisibility(View.VISIBLE); 201 } else { 202 animation.setAnimationListener(new AnimationListener() { 203 @Override 204 public void onAnimationStart(Animation animation) { 205 } 206 207 @Override 208 public void onAnimationRepeat(Animation animation) { 209 } 210 211 @Override 212 public void onAnimationEnd(Animation animation) { 213 view.setVisibility(View.INVISIBLE); 214 } 215 }); 216 } 217 view.startAnimation(animation); 218 } else { 219 view.setVisibility(shown ? View.VISIBLE : View.INVISIBLE); 220 } 221 } 222 223 /** 224 * @return the id of the string to display when there are no entries to show. 225 */ getEmptyViewString()226 public int getEmptyViewString() { 227 return R.string.no_permissions; 228 } 229 } 230