1 /* 2 * Copyright (C) 2018 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.customization.picker.grid; 17 18 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; 19 20 import android.app.Activity; 21 import android.content.Context; 22 import android.content.res.Resources; 23 import android.graphics.drawable.BitmapDrawable; 24 import android.net.Uri; 25 import android.os.Bundle; 26 import android.util.DisplayMetrics; 27 import android.util.Log; 28 import android.view.LayoutInflater; 29 import android.view.SurfaceHolder; 30 import android.view.SurfaceView; 31 import android.view.View; 32 import android.view.View.OnLayoutChangeListener; 33 import android.view.ViewGroup; 34 import android.widget.ImageView; 35 36 import androidx.annotation.NonNull; 37 import androidx.annotation.Nullable; 38 import androidx.cardview.widget.CardView; 39 import androidx.core.widget.ContentLoadingProgressBar; 40 import androidx.recyclerview.widget.RecyclerView; 41 42 import com.android.customization.model.CustomizationManager.Callback; 43 import com.android.customization.model.CustomizationManager.OptionsFetchedListener; 44 import com.android.customization.model.grid.GridOption; 45 import com.android.customization.model.grid.GridOptionsManager; 46 import com.android.customization.module.ThemesUserEventLogger; 47 import com.android.customization.picker.BasePreviewAdapter; 48 import com.android.customization.picker.BasePreviewAdapter.PreviewPage; 49 import com.android.customization.widget.OptionSelectorController; 50 import com.android.systemui.shared.system.SurfaceViewRequestUtils; 51 import com.android.wallpaper.R; 52 import com.android.wallpaper.asset.Asset; 53 import com.android.wallpaper.asset.ContentUriAsset; 54 import com.android.wallpaper.model.WallpaperInfo; 55 import com.android.wallpaper.module.CurrentWallpaperInfoFactory; 56 import com.android.wallpaper.module.InjectorProvider; 57 import com.android.wallpaper.picker.ToolbarFragment; 58 import com.android.wallpaper.widget.PreviewPager; 59 60 import com.bumptech.glide.Glide; 61 import com.bumptech.glide.request.RequestOptions; 62 63 import java.util.List; 64 65 /** 66 * Fragment that contains the UI for selecting and applying a GridOption. 67 */ 68 public class GridFragment extends ToolbarFragment { 69 70 private static final int PREVIEW_FADE_DURATION_MS = 100; 71 72 private static final String TAG = "GridFragment"; 73 74 /** 75 * Interface to be implemented by an Activity hosting a {@link GridFragment} 76 */ 77 public interface GridFragmentHost { getGridOptionsManager()78 GridOptionsManager getGridOptionsManager(); 79 } 80 newInstance(CharSequence title)81 public static GridFragment newInstance(CharSequence title) { 82 GridFragment fragment = new GridFragment(); 83 fragment.setArguments(ToolbarFragment.createArguments(title)); 84 return fragment; 85 } 86 87 private WallpaperInfo mHomeWallpaper; 88 private float mScreenAspectRatio; 89 private int mCardHeight; 90 private int mCardWidth; 91 private BitmapDrawable mCardBackground; 92 private GridPreviewAdapter mAdapter; 93 private RecyclerView mOptionsContainer; 94 private OptionSelectorController<GridOption> mOptionsController; 95 private GridOptionsManager mGridManager; 96 private GridOption mSelectedOption; 97 private PreviewPager mPreviewPager; 98 private ContentLoadingProgressBar mLoading; 99 private View mContent; 100 private View mError; 101 private ThemesUserEventLogger mEventLogger; 102 103 @Override onAttach(Context context)104 public void onAttach(Context context) { 105 super.onAttach(context); 106 mGridManager = ((GridFragmentHost) context).getGridOptionsManager(); 107 mEventLogger = (ThemesUserEventLogger) 108 InjectorProvider.getInjector().getUserEventLogger(context); 109 } 110 111 @Nullable 112 @Override onCreateView(@onNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState)113 public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, 114 @Nullable Bundle savedInstanceState) { 115 View view; 116 if (ADD_SCALABLE_HEADER) { 117 // TODO(b/147780560): Once the temporary flag (ADD_SCALABLE_HEADER) is removed, 118 // we should have a layout with the same name for portrait and landscape. 119 int orientation = getResources().getConfiguration().orientation; 120 view = inflater.inflate( 121 orientation == ORIENTATION_LANDSCAPE 122 ? R.layout.fragment_grid_picker 123 : R.layout.fragment_grid_scalable_picker, 124 container, /* attachToRoot */ false); 125 } else { 126 view = inflater.inflate( 127 R.layout.fragment_grid_picker, container, /* attachToRoot */ false); 128 } 129 setUpToolbar(view); 130 mContent = view.findViewById(R.id.content_section); 131 mPreviewPager = view.findViewById(R.id.grid_preview_pager); 132 mOptionsContainer = view.findViewById(R.id.options_container); 133 mLoading = view.findViewById(R.id.loading_indicator); 134 mError = view.findViewById(R.id.error_section); 135 final Resources res = getResources(); 136 DisplayMetrics dm = res.getDisplayMetrics(); 137 mScreenAspectRatio = (float) dm.heightPixels / dm.widthPixels; 138 139 // Clear memory cache whenever grid fragment view is being loaded. 140 Glide.get(getContext()).clearMemory(); 141 setUpOptions(); 142 view.findViewById(R.id.apply_button).setOnClickListener(v -> { 143 mGridManager.apply(mSelectedOption, new Callback() { 144 @Override 145 public void onSuccess() { 146 getActivity().finish(); 147 } 148 149 @Override 150 public void onError(@Nullable Throwable throwable) { 151 //TODO(santie): handle 152 } 153 }); 154 155 }); 156 CurrentWallpaperInfoFactory factory = InjectorProvider.getInjector() 157 .getCurrentWallpaperFactory(getContext().getApplicationContext()); 158 159 factory.createCurrentWallpaperInfos((homeWallpaper, lockWallpaper, presentationMode) -> { 160 mHomeWallpaper = homeWallpaper; 161 loadWallpaperBackground(); 162 163 }, false); 164 view.addOnLayoutChangeListener(new OnLayoutChangeListener() { 165 @Override 166 public void onLayoutChange(View v, int left, int top, int right, int bottom, 167 int oldLeft, int oldTop, int oldRight, int oldBottom) { 168 mCardHeight = mPreviewPager.getHeight() - mPreviewPager.getPaddingTop() - 169 res.getDimensionPixelSize(R.dimen.indicator_container_height); 170 mCardWidth = (int) (mCardHeight / mScreenAspectRatio); 171 view.removeOnLayoutChangeListener(this); 172 loadWallpaperBackground(); 173 } 174 }); 175 return view; 176 } 177 loadWallpaperBackground()178 private void loadWallpaperBackground() { 179 if (mHomeWallpaper != null && mCardHeight > 0 && mCardWidth > 0) { 180 mHomeWallpaper.getThumbAsset(getContext()).decodeBitmap(mCardWidth, 181 mCardHeight, 182 bitmap -> { 183 mCardBackground = 184 new BitmapDrawable(getResources(), bitmap); 185 if (mAdapter != null) { 186 mAdapter.onWallpaperInfoLoaded(); 187 } 188 }); 189 } 190 } 191 createAdapter()192 private void createAdapter() { 193 mAdapter = new GridPreviewAdapter(mSelectedOption); 194 mPreviewPager.setAdapter(mAdapter); 195 } 196 setUpOptions()197 private void setUpOptions() { 198 hideError(); 199 mLoading.show(); 200 mGridManager.fetchOptions(new OptionsFetchedListener<GridOption>() { 201 @Override 202 public void onOptionsLoaded(List<GridOption> options) { 203 mLoading.hide(); 204 mOptionsController = new OptionSelectorController<>(mOptionsContainer, options); 205 206 mOptionsController.addListener(selected -> { 207 mSelectedOption = (GridOption) selected; 208 mEventLogger.logGridSelected(mSelectedOption); 209 createAdapter(); 210 }); 211 mOptionsController.initOptions(mGridManager); 212 for (GridOption option : options) { 213 if (option.isActive(mGridManager)) { 214 mSelectedOption = option; 215 } 216 } 217 // For development only, as there should always be a grid set. 218 if (mSelectedOption == null) { 219 mSelectedOption = options.get(0); 220 } 221 createAdapter(); 222 } 223 224 @Override 225 public void onError(@Nullable Throwable throwable) { 226 if (throwable != null) { 227 Log.e(TAG, "Error loading grid options", throwable); 228 } 229 showError(); 230 } 231 }, false); 232 } 233 hideError()234 private void hideError() { 235 mContent.setVisibility(View.VISIBLE); 236 mError.setVisibility(View.GONE); 237 } 238 showError()239 private void showError() { 240 mLoading.hide(); 241 mContent.setVisibility(View.GONE); 242 mError.setVisibility(View.VISIBLE); 243 } 244 245 private class GridPreviewPage extends PreviewPage { 246 private final int mPageId; 247 private final Asset mPreviewAsset; 248 private final int mCols; 249 private final int mRows; 250 private final Activity mActivity; 251 252 private final String mName; 253 254 private ImageView mPreview; 255 private SurfaceView mPreviewSurface; 256 GridPreviewPage(Activity activity, int id, Uri previewUri, String name, int rows, int cols)257 private GridPreviewPage(Activity activity, int id, Uri previewUri, String name, int rows, 258 int cols) { 259 super(null); 260 mPageId = id; 261 mPreviewAsset = new ContentUriAsset(activity, previewUri, 262 RequestOptions.fitCenterTransform()); 263 mName = name; 264 mRows = rows; 265 mCols = cols; 266 mActivity = activity; 267 } 268 269 @Override setCard(CardView card)270 public void setCard(CardView card) { 271 super.setCard(card); 272 mPreview = card.findViewById(R.id.grid_preview_image); 273 mPreviewSurface = card.findViewById(R.id.grid_preview_surface); 274 } 275 bindPreviewContent()276 public void bindPreviewContent() { 277 Resources resources = card.getResources(); 278 bindWallpaperIfAvailable(); 279 final boolean usesSurfaceViewForPreview = mGridManager.usesSurfaceView(); 280 mPreview.setVisibility(usesSurfaceViewForPreview ? View.GONE : View.VISIBLE); 281 mPreviewSurface.setVisibility(usesSurfaceViewForPreview ? View.VISIBLE : View.GONE); 282 if (usesSurfaceViewForPreview) { 283 mPreviewSurface.setZOrderOnTop(true); 284 mPreviewSurface.getHolder().addCallback(new SurfaceHolder.Callback() { 285 @Override 286 public void surfaceCreated(SurfaceHolder holder) { 287 Bundle bundle = SurfaceViewRequestUtils.createSurfaceBundle( 288 mPreviewSurface); 289 mGridManager.renderPreview(bundle, mName); 290 } 291 292 @Override 293 public void surfaceChanged(SurfaceHolder holder, int format, int width, 294 int height) {} 295 296 @Override 297 public void surfaceDestroyed(SurfaceHolder holder) {} 298 }); 299 } else { 300 mPreviewAsset.loadDrawableWithTransition(mActivity, 301 mPreview /* imageView */, 302 PREVIEW_FADE_DURATION_MS /* duration */, 303 null /* drawableLoadedListener */, 304 resources.getColor(android.R.color.transparent, 305 null) /* placeHolderColorJ */); 306 } 307 } 308 bindWallpaperIfAvailable()309 void bindWallpaperIfAvailable() { 310 if (card != null && mCardBackground != null) { 311 mPreview.setBackground(mCardBackground); 312 mPreviewSurface.setBackground(mCardBackground); 313 } 314 } 315 } 316 /** 317 * Adapter class for mPreviewPager. 318 * This is a ViewPager as it allows for a nice pagination effect (ie, pages snap on swipe, 319 * we don't want to just scroll) 320 */ 321 class GridPreviewAdapter extends BasePreviewAdapter<GridPreviewPage> { 322 GridPreviewAdapter(GridOption gridOption)323 GridPreviewAdapter(GridOption gridOption) { 324 super(getContext(), R.layout.grid_preview_card); 325 for (int i = 0; i < gridOption.previewPagesCount; i++) { 326 addPage(new GridPreviewPage(getActivity(), i, 327 gridOption.previewImageUri.buildUpon().appendPath("" + i).build(), 328 gridOption.name, gridOption.rows, gridOption.cols)); 329 } 330 } 331 onWallpaperInfoLoaded()332 void onWallpaperInfoLoaded() { 333 for (GridPreviewPage page : mPages) { 334 page.bindWallpaperIfAvailable(); 335 } 336 } 337 } 338 } 339