1 /* 2 * Copyright (C) 2021 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 package com.android.wallpaper.picker; 17 18 import android.content.Context; 19 import android.os.Bundle; 20 import android.util.Log; 21 import android.view.LayoutInflater; 22 import android.view.View; 23 import android.view.ViewGroup; 24 25 import androidx.annotation.Nullable; 26 import androidx.core.widget.NestedScrollView; 27 import androidx.fragment.app.Fragment; 28 import androidx.fragment.app.FragmentManager; 29 import androidx.lifecycle.ViewModelProvider; 30 31 import com.android.wallpaper.R; 32 import com.android.wallpaper.model.CustomizationSectionController; 33 import com.android.wallpaper.model.CustomizationSectionController.CustomizationSectionNavigationController; 34 import com.android.wallpaper.model.PermissionRequester; 35 import com.android.wallpaper.model.WallpaperColorsViewModel; 36 import com.android.wallpaper.model.WallpaperPreviewNavigator; 37 import com.android.wallpaper.model.WorkspaceViewModel; 38 import com.android.wallpaper.module.CustomizationSections; 39 import com.android.wallpaper.module.InjectorProvider; 40 import com.android.wallpaper.util.ActivityUtils; 41 42 import java.util.ArrayList; 43 import java.util.List; 44 import java.util.stream.Collectors; 45 46 /** The Fragment UI for customization sections. */ 47 public class CustomizationPickerFragment extends AppbarFragment implements 48 CustomizationSectionNavigationController { 49 50 private static final String TAG = "CustomizationPickerFragment"; 51 private static final String SCROLL_POSITION_Y = "SCROLL_POSITION_Y"; 52 53 // Note that the section views will be displayed by the list ordering. 54 private final List<CustomizationSectionController<?>> mSectionControllers = new ArrayList<>(); 55 private NestedScrollView mNestedScrollView; 56 @Nullable private Bundle mBackStackSavedInstanceState; 57 58 @Override onCreateView(LayoutInflater inflater, ViewGroup container, @Nullable Bundle savedInstanceState)59 public View onCreateView(LayoutInflater inflater, ViewGroup container, 60 @Nullable Bundle savedInstanceState) { 61 final View view = inflater.inflate(R.layout.collapsing_toolbar_base_layout, 62 container, /* attachToRoot= */ false); 63 setContentView(view, R.layout.fragment_customization_picker); 64 setUpToolbar(view, ActivityUtils.isLaunchedFromSettingsRelated(getActivity().getIntent())); 65 66 ViewGroup sectionContainer = view.findViewById(R.id.section_container); 67 sectionContainer.setOnApplyWindowInsetsListener((v, windowInsets) -> { 68 v.setPadding( 69 v.getPaddingLeft(), 70 v.getPaddingTop(), 71 v.getPaddingRight(), 72 windowInsets.getSystemWindowInsetBottom()); 73 return windowInsets.consumeSystemWindowInsets(); 74 }); 75 mNestedScrollView = view.findViewById(R.id.scroll_container); 76 77 if (mBackStackSavedInstanceState != null) { 78 savedInstanceState = mBackStackSavedInstanceState; 79 mBackStackSavedInstanceState = null; 80 } 81 82 initSections(savedInstanceState); 83 mSectionControllers.forEach(controller -> 84 mNestedScrollView.post(() -> { 85 final Context context = getContext(); 86 if (context == null) { 87 Log.w(TAG, "Adding section views with null context"); 88 return; 89 } 90 sectionContainer.addView(controller.createView(context)); 91 } 92 ) 93 ); 94 final Bundle savedInstanceStateRef = savedInstanceState; 95 // Post it to the end of adding views to ensure restoring view state the last task. 96 mNestedScrollView.post(() -> 97 restoreViewState(savedInstanceStateRef) 98 ); 99 return view; 100 } 101 setContentView(View view, int layoutResId)102 private void setContentView(View view, int layoutResId) { 103 final ViewGroup parent = view.findViewById(R.id.content_frame); 104 if (parent != null) { 105 parent.removeAllViews(); 106 } 107 LayoutInflater.from(view.getContext()).inflate(layoutResId, parent); 108 } 109 restoreViewState(@ullable Bundle savedInstanceState)110 private void restoreViewState(@Nullable Bundle savedInstanceState) { 111 if (savedInstanceState != null) { 112 mNestedScrollView.post(() -> 113 mNestedScrollView.setScrollY(savedInstanceState.getInt(SCROLL_POSITION_Y))); 114 } 115 } 116 117 @Override onSaveInstanceState(Bundle savedInstanceState)118 public void onSaveInstanceState(Bundle savedInstanceState) { 119 onSaveInstanceStateInternal(savedInstanceState); 120 super.onSaveInstanceState(savedInstanceState); 121 } 122 123 @Override getToolbarId()124 protected int getToolbarId() { 125 return R.id.action_bar; 126 } 127 128 @Override getToolbarColorId()129 protected int getToolbarColorId() { 130 return android.R.color.transparent; 131 } 132 133 @Override getDefaultTitle()134 public CharSequence getDefaultTitle() { 135 return getString(R.string.app_name); 136 } 137 138 @Override onBackPressed()139 public boolean onBackPressed() { 140 // TODO(b/191120122) Improve glitchy animation in Settings. 141 if (ActivityUtils.isLaunchedFromSettingsSearch(getActivity().getIntent())) { 142 mSectionControllers.forEach(CustomizationSectionController::onTransitionOut); 143 } 144 return super.onBackPressed(); 145 } 146 147 @Override onDestroyView()148 public void onDestroyView() { 149 // When add to back stack, #onDestroyView would be called, but #onDestroy wouldn't. So 150 // storing the state in variable to restore when back to foreground. If it's not a back 151 // stack case (i,e, config change), the variable would not be retained, see 152 // https://developer.android.com/guide/fragments/saving-state. 153 mBackStackSavedInstanceState = new Bundle(); 154 onSaveInstanceStateInternal(mBackStackSavedInstanceState); 155 156 mSectionControllers.forEach(CustomizationSectionController::release); 157 mSectionControllers.clear(); 158 super.onDestroyView(); 159 } 160 161 @Override navigateTo(Fragment fragment)162 public void navigateTo(Fragment fragment) { 163 FragmentManager fragmentManager = getActivity().getSupportFragmentManager(); 164 fragmentManager 165 .beginTransaction() 166 .replace(R.id.fragment_container, fragment) 167 .addToBackStack(null) 168 .commit(); 169 fragmentManager.executePendingTransactions(); 170 } 171 172 /** Saves state of the fragment. */ onSaveInstanceStateInternal(Bundle savedInstanceState)173 private void onSaveInstanceStateInternal(Bundle savedInstanceState) { 174 if (mNestedScrollView != null) { 175 savedInstanceState.putInt(SCROLL_POSITION_Y, mNestedScrollView.getScrollY()); 176 } 177 mSectionControllers.forEach(c -> c.onSaveInstanceState(savedInstanceState)); 178 } 179 initSections(@ullable Bundle savedInstanceState)180 private void initSections(@Nullable Bundle savedInstanceState) { 181 // Release and clear if any. 182 mSectionControllers.forEach(CustomizationSectionController::release); 183 mSectionControllers.clear(); 184 185 WallpaperColorsViewModel wcViewModel = new ViewModelProvider(getActivity()) 186 .get(WallpaperColorsViewModel.class); 187 WorkspaceViewModel workspaceViewModel = new ViewModelProvider(getActivity()) 188 .get(WorkspaceViewModel.class); 189 190 CustomizationSections sections = InjectorProvider.getInjector().getCustomizationSections(); 191 List<CustomizationSectionController<?>> allSectionControllers = 192 sections.getAllSectionControllers(getActivity(), getViewLifecycleOwner(), 193 wcViewModel, workspaceViewModel, getPermissionRequester(), 194 getWallpaperPreviewNavigator(), this, savedInstanceState); 195 196 mSectionControllers.addAll(getAvailableSections(allSectionControllers)); 197 } 198 getAvailableSections( List<CustomizationSectionController<?>> controllers)199 protected List<CustomizationSectionController<?>> getAvailableSections( 200 List<CustomizationSectionController<?>> controllers) { 201 return controllers.stream() 202 .filter(controller -> { 203 if(controller.isAvailable(getContext())) { 204 return true; 205 } else { 206 controller.release(); 207 Log.d(TAG, "Section is not available: " + controller); 208 return false; 209 }}) 210 .collect(Collectors.toList()); 211 } 212 213 private PermissionRequester getPermissionRequester() { 214 return (PermissionRequester) getActivity(); 215 } 216 217 private WallpaperPreviewNavigator getWallpaperPreviewNavigator() { 218 return (WallpaperPreviewNavigator) getActivity(); 219 } 220 } 221