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