blob: 33767eeb7595bc6e2570457db7ff16c7b6a20b44 [file] [log] [blame]
Jon Miranda16ea1b12017-12-12 14:52:48 -08001/*
2 * Copyright (C) 2017 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 */
16package com.android.wallpaper.picker;
17
18import android.animation.Animator;
19import android.animation.AnimatorListenerAdapter;
20import android.annotation.TargetApi;
21import android.app.Activity;
22import android.app.ProgressDialog;
23import android.content.Context;
24import android.content.Intent;
25import android.content.pm.ActivityInfo;
26import android.content.res.Configuration;
27import android.content.res.Resources.NotFoundException;
28import android.graphics.Bitmap;
29import android.graphics.Bitmap.Config;
30import android.graphics.Color;
31import android.graphics.Point;
Santiago Etchebehere6af67ed2018-03-27 16:37:57 -070032import android.graphics.PointF;
Jon Miranda16ea1b12017-12-12 14:52:48 -080033import android.graphics.PorterDuff.Mode;
34import android.graphics.Rect;
Jon Miranda16ea1b12017-12-12 14:52:48 -080035import android.graphics.drawable.Drawable;
36import android.net.Uri;
Jon Miranda623e57e2018-01-05 16:33:30 -080037import android.os.Build;
Jon Miranda16ea1b12017-12-12 14:52:48 -080038import android.os.Build.VERSION;
39import android.os.Build.VERSION_CODES;
40import android.os.Bundle;
41import android.os.Handler;
42import android.support.annotation.IntDef;
Jon Miranda16ea1b12017-12-12 14:52:48 -080043import android.support.design.widget.BottomSheetBehavior;
44import android.support.design.widget.BottomSheetBehavior.State;
45import android.support.v4.app.DialogFragment;
46import android.support.v4.app.Fragment;
Santiago Etchebeheref790d702018-06-07 13:04:13 -070047import android.support.v4.app.FragmentActivity;
Jon Miranda16ea1b12017-12-12 14:52:48 -080048import android.support.v4.view.ViewCompat;
49import android.support.v7.app.AppCompatActivity;
50import android.support.v7.widget.Toolbar;
51import android.util.Log;
52import android.view.Display;
53import android.view.LayoutInflater;
54import android.view.Menu;
55import android.view.MenuInflater;
56import android.view.MenuItem;
57import android.view.Surface;
58import android.view.View;
59import android.view.View.OnClickListener;
60import android.view.ViewGroup;
61import android.view.Window;
62import android.widget.Button;
63import android.widget.FrameLayout;
64import android.widget.ImageView;
65import android.widget.LinearLayout;
66import android.widget.TextView;
67import android.widget.Toast;
68
69import com.android.wallpaper.R;
70import com.android.wallpaper.asset.Asset;
71import com.android.wallpaper.asset.Asset.BitmapReceiver;
72import com.android.wallpaper.asset.Asset.DimensionsReceiver;
Jon Miranda16ea1b12017-12-12 14:52:48 -080073import com.android.wallpaper.compat.BuildCompat;
74import com.android.wallpaper.compat.ButtonDrawableSetterCompat;
75import com.android.wallpaper.config.Flags;
Santiago Etchebehere8f118fe2018-06-11 16:57:10 -070076import com.android.wallpaper.model.LiveWallpaperInfo;
Jon Miranda16ea1b12017-12-12 14:52:48 -080077import com.android.wallpaper.model.WallpaperInfo;
Santiago Etchebehere8f118fe2018-06-11 16:57:10 -070078import com.android.wallpaper.module.CurrentWallpaperInfoFactory;
Jon Miranda16ea1b12017-12-12 14:52:48 -080079import com.android.wallpaper.module.ExploreIntentChecker;
80import com.android.wallpaper.module.Injector;
81import com.android.wallpaper.module.InjectorProvider;
82import com.android.wallpaper.module.UserEventLogger;
83import com.android.wallpaper.module.UserEventLogger.WallpaperSetFailureReason;
84import com.android.wallpaper.module.WallpaperPersister;
85import com.android.wallpaper.module.WallpaperPersister.Destination;
86import com.android.wallpaper.module.WallpaperPersister.SetWallpaperCallback;
87import com.android.wallpaper.module.WallpaperPreferences;
88import com.android.wallpaper.util.ScreenSizeCalculator;
89import com.android.wallpaper.util.ThrowableAnalyzer;
90import com.android.wallpaper.util.WallpaperCropUtils;
91import com.android.wallpaper.widget.MaterialProgressDrawable;
92import com.bumptech.glide.Glide;
93import com.bumptech.glide.MemoryCategory;
Jon Miranda623e57e2018-01-05 16:33:30 -080094import com.davemorrissey.labs.subscaleview.ImageSource;
95import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView;
Jon Miranda16ea1b12017-12-12 14:52:48 -080096
97import java.util.Date;
98import java.util.List;
99
100/**
101 * Fragment which displays the UI for previewing an individual wallpaper and its attribution
102 * information.
103 */
104public class PreviewFragment extends Fragment implements
105 SetWallpaperDialogFragment.Listener, SetWallpaperErrorDialogFragment.Listener,
106 LoadWallpaperErrorDialogFragment.Listener {
107
108 /**
109 * User can view wallpaper and attributions in full screen, but "Set wallpaper" button is hidden.
110 */
111 public static final int MODE_VIEW_ONLY = 0;
112
113 /**
114 * User can view wallpaper and attributions in full screen and click "Set wallpaper" to set the
115 * wallpaper with pan and crop position to the device.
116 */
117 public static final int MODE_CROP_AND_SET_WALLPAPER = 1;
Jon Miranda623e57e2018-01-05 16:33:30 -0800118
119 /**
120 * Possible preview modes for the fragment.
121 */
122 @IntDef({
123 MODE_VIEW_ONLY,
124 MODE_CROP_AND_SET_WALLPAPER})
125 public @interface PreviewMode {
126 }
127
Clément Julliardea1638d2018-05-21 19:15:17 -0700128 protected static final String ARG_WALLPAPER = "wallpaper";
129 protected static final String ARG_PREVIEW_MODE = "preview_mode";
130 protected static final String ARG_TESTING_MODE_ENABLED = "testing_mode_enabled";
Jon Miranda16ea1b12017-12-12 14:52:48 -0800131 private static final String TAG_LOAD_WALLPAPER_ERROR_DIALOG_FRAGMENT =
132 "load_wallpaper_error_dialog";
133 private static final String TAG_SET_WALLPAPER_DIALOG_FRAGMENT = "set_wallpaper_dialog";
134 private static final String TAG_SET_WALLPAPER_ERROR_DIALOG_FRAGMENT =
135 "set_wallpaper_error_dialog";
136 private static final int UNUSED_REQUEST_CODE = 1;
137 private static final float DEFAULT_WALLPAPER_MAX_ZOOM = 8f;
138 private static final String TAG = "PreviewFragment";
139 private static final String PROGRESS_DIALOG_NO_TITLE = null;
140 private static final boolean PROGRESS_DIALOG_INDETERMINATE = true;
141 private static final float PAGE_BITMAP_MAX_HEAP_RATIO = 0.25f;
142 private static final String KEY_BOTTOM_SHEET_STATE = "key_bottom_sheet_state";
Jon Miranda623e57e2018-01-05 16:33:30 -0800143
Jon Miranda16ea1b12017-12-12 14:52:48 -0800144 @PreviewMode
145 private int mPreviewMode;
Jon Miranda623e57e2018-01-05 16:33:30 -0800146
Jon Miranda16ea1b12017-12-12 14:52:48 -0800147 /**
148 * When true, enables a test mode of operation -- in which certain UI features are disabled to
149 * allow for UI tests to run correctly. Works around issue in ProgressDialog currently where the
150 * dialog constantly keeps the UI thread alive and blocks a test forever.
151 */
152 private boolean mTestingModeEnabled;
Jon Miranda623e57e2018-01-05 16:33:30 -0800153
Clément Julliardea1638d2018-05-21 19:15:17 -0700154 protected SubsamplingScaleImageView mFullResImageView;
155 protected WallpaperInfo mWallpaper;
Jon Miranda16ea1b12017-12-12 14:52:48 -0800156 private Asset mWallpaperAsset;
157 private WallpaperPersister mWallpaperPersister;
158 private WallpaperPreferences mPreferences;
159 private UserEventLogger mUserEventLogger;
160 private LinearLayout mBottomSheet;
161 private TextView mAttributionTitle;
162 private TextView mAttributionSubtitle1;
163 private TextView mAttributionSubtitle2;
164 private FrameLayout mAttributionExploreSection;
165 private Button mAttributionExploreButton;
166 private ImageView mPreviewPaneArrow;
167 private int mCurrentScreenOrientation;
168 private ProgressDialog mProgressDialog;
169 private Point mDefaultCropSurfaceSize;
170 private Point mScreenSize;
171 private Point mRawWallpaperSize; // Native size of wallpaper image.
172 private ImageView mLoadingIndicator;
173 private MaterialProgressDrawable mProgressDrawable;
174 private ImageView mLowResImageView;
Jon Miranda623e57e2018-01-05 16:33:30 -0800175
Jon Miranda16ea1b12017-12-12 14:52:48 -0800176 @SuppressWarnings("RestrictTo")
177 @State
178 private int mBottomSheetInitialState;
Jon Miranda623e57e2018-01-05 16:33:30 -0800179
Jon Miranda16ea1b12017-12-12 14:52:48 -0800180 private Intent mExploreIntent;
Jon Miranda623e57e2018-01-05 16:33:30 -0800181
Jon Miranda16ea1b12017-12-12 14:52:48 -0800182 /**
183 * Staged error dialog fragments that were unable to be shown when the hosting activity didn't
184 * allow committing fragment transactions.
185 */
186 private SetWallpaperErrorDialogFragment mStagedSetWallpaperErrorDialogFragment;
187 private LoadWallpaperErrorDialogFragment mStagedLoadWallpaperErrorDialogFragment;
188
189 /**
190 * Creates and returns new instance of {@link PreviewFragment} with the provided wallpaper set as
191 * an argument.
192 */
193 public static PreviewFragment newInstance(
194 WallpaperInfo wallpaperInfo, @PreviewMode int mode, boolean testingModeEnabled) {
195 Bundle args = new Bundle();
196 args.putParcelable(ARG_WALLPAPER, wallpaperInfo);
197 args.putInt(ARG_PREVIEW_MODE, mode);
198 args.putBoolean(ARG_TESTING_MODE_ENABLED, testingModeEnabled);
199
200 PreviewFragment fragment = new PreviewFragment();
201 fragment.setArguments(args);
202 return fragment;
203 }
204
Jon Miranda16ea1b12017-12-12 14:52:48 -0800205 @Override
206 public void onCreate(Bundle savedInstanceState) {
207 super.onCreate(savedInstanceState);
208
209 Activity activity = getActivity();
210 Context appContext = activity.getApplicationContext();
211 Injector injector = InjectorProvider.getInjector();
212
213 mWallpaperPersister = injector.getWallpaperPersister(appContext);
214 mPreferences = injector.getPreferences(appContext);
215 mUserEventLogger = injector.getUserEventLogger(appContext);
216 mWallpaper = getArguments().getParcelable(ARG_WALLPAPER);
217 mWallpaperAsset = mWallpaper.getAsset(appContext);
218 //noinspection ResourceType
219 mPreviewMode = getArguments().getInt(ARG_PREVIEW_MODE);
220 mTestingModeEnabled = getArguments().getBoolean(ARG_TESTING_MODE_ENABLED);
221
222 setHasOptionsMenu(true);
223
224 // Allow the layout to draw fullscreen even behind the status bar, so we can set as the status
225 // bar color a color that has a custom translucency in the theme.
226 Window window = activity.getWindow();
227 window.getDecorView().setSystemUiVisibility(
228 View.SYSTEM_UI_FLAG_LAYOUT_STABLE
229 | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
230 | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
231
232 List<String> attributions = mWallpaper.getAttributions(activity);
233 if (attributions.size() > 0 && attributions.get(0) != null) {
234 activity.setTitle(attributions.get(0));
235 }
236 }
237
238 @Override
239 public View onCreateView(LayoutInflater inflater, ViewGroup container,
240 Bundle savedInstanceState) {
241 View view = inflater.inflate(R.layout.fragment_preview, container, false);
242
243 // Set toolbar as the action bar.
244 Toolbar toolbar = (Toolbar) view.findViewById(R.id.toolbar);
245 ((AppCompatActivity) getActivity()).setSupportActionBar(toolbar);
246 ((AppCompatActivity) getActivity()).getSupportActionBar().setDisplayHomeAsUpEnabled(true);
247 ((AppCompatActivity) getActivity()).getSupportActionBar().setDisplayShowTitleEnabled(false);
248
249 // Use updated fancy arrow icon for O+.
250 if (BuildCompat.isAtLeastO()) {
251 Drawable navigationIcon = getResources().getDrawable(
252 R.drawable.material_ic_arrow_back_black_24);
253
254 // This Drawable's state is shared across the app, so make a copy of it before applying a
255 // color tint as not to affect other clients elsewhere in the app.
256 navigationIcon = navigationIcon.getConstantState().newDrawable().mutate();
257 navigationIcon.setColorFilter(
258 getResources().getColor(R.color.material_white_100), Mode.SRC_IN);
259 navigationIcon.setAutoMirrored(true);
260 toolbar.setNavigationIcon(navigationIcon);
261 }
262
263 ViewCompat.setPaddingRelative(toolbar,
264 /* start */ getResources().getDimensionPixelSize(
265 R.dimen.preview_toolbar_up_button_start_padding),
266 /* top */ 0,
267 /* end */ getResources().getDimensionPixelSize(
268 R.dimen.preview_toolbar_set_wallpaper_button_end_padding),
269 /* bottom */ 0);
270
Santiago Etchebehere41257a82018-03-30 10:50:12 -0700271 mFullResImageView = view.findViewById(R.id.full_res_image);
Jon Miranda16ea1b12017-12-12 14:52:48 -0800272 mLoadingIndicator = (ImageView) view.findViewById(R.id.loading_indicator);
273
274 mBottomSheet = (LinearLayout) view.findViewById(R.id.bottom_sheet);
275 mAttributionTitle = (TextView) view.findViewById(R.id.preview_attribution_pane_title);
276 mAttributionSubtitle1 = (TextView) view.findViewById(R.id.preview_attribution_pane_subtitle1);
277 mAttributionSubtitle2 = (TextView) view.findViewById(R.id.preview_attribution_pane_subtitle2);
278 mAttributionExploreSection = (FrameLayout) view.findViewById(
279 R.id.preview_attribution_pane_explore_section);
280 mAttributionExploreButton = (Button) view.findViewById(
281 R.id.preview_attribution_pane_explore_button);
282 mPreviewPaneArrow = (ImageView) view.findViewById(R.id.preview_attribution_pane_arrow);
283 mLowResImageView = (ImageView) view.findViewById(R.id.low_res_image);
284
285 mPreviewPaneArrow.setColorFilter(
286 getResources().getColor(R.color.preview_pane_arrow_color), Mode.SRC_IN);
287
288 // Trim some memory from Glide to make room for the full-size image in this fragment.
289 Glide.get(getActivity()).setMemoryCategory(MemoryCategory.LOW);
290
291 mDefaultCropSurfaceSize = WallpaperCropUtils.getDefaultCropSurfaceSize(
292 getResources(), getActivity().getWindowManager().getDefaultDisplay());
293 mScreenSize = ScreenSizeCalculator.getInstance().getScreenSize(
294 getActivity().getWindowManager().getDefaultDisplay());
295
296 // Load a low-res placeholder image if there's a thumbnail available from the asset that can be
297 // shown to the user more quickly than the full-sized image.
298 if (mWallpaperAsset.hasLowResDataSource()) {
299 mWallpaperAsset.loadLowResDrawable(getActivity(), mLowResImageView, Color.BLACK,
300 new WallpaperPreviewBitmapTransformation(getActivity().getApplicationContext(), isRtl()));
301 }
302
303 mWallpaperAsset.decodeRawDimensions(getActivity(), new DimensionsReceiver() {
304 @Override
305 public void onDimensionsDecoded(Point dimensions) {
306 // Don't continue loading the wallpaper if the Fragment is detached.
307 Activity activity = getActivity();
308 if (activity == null) {
309 return;
310 }
311
312 // Return early and show a dialog if dimensions are null (signaling a decoding error).
313 if (dimensions == null) {
314 showLoadWallpaperErrorDialog();
315 return;
316 }
317
318 mRawWallpaperSize = dimensions;
Jon Miranda16ea1b12017-12-12 14:52:48 -0800319 ExploreIntentChecker intentChecker =
320 InjectorProvider.getInjector().getExploreIntentChecker(activity);
321 String actionUrl = mWallpaper.getActionUrl(activity);
322 if (actionUrl != null && !actionUrl.isEmpty()) {
323 Uri exploreUri = Uri.parse(mWallpaper.getActionUrl(activity));
324
Jon Miranda623e57e2018-01-05 16:33:30 -0800325 intentChecker.fetchValidActionViewIntent(exploreUri, exploreIntent -> {
326 if (getActivity() == null) {
327 return;
328 }
Jon Miranda16ea1b12017-12-12 14:52:48 -0800329
Jon Miranda623e57e2018-01-05 16:33:30 -0800330 mExploreIntent = exploreIntent;
Santiago Etchebehere41257a82018-03-30 10:50:12 -0700331 initFullResView();
Jon Miranda16ea1b12017-12-12 14:52:48 -0800332 });
333 } else {
Santiago Etchebehere41257a82018-03-30 10:50:12 -0700334 initFullResView();
Jon Miranda16ea1b12017-12-12 14:52:48 -0800335 }
336 }
337 });
338
339 // Configure loading indicator with a MaterialProgressDrawable.
340 mProgressDrawable =
341 new MaterialProgressDrawable(getActivity().getApplicationContext(), mLoadingIndicator);
342 mProgressDrawable.setAlpha(255);
343 mProgressDrawable.setBackgroundColor(getResources().getColor(R.color.material_white_100));
344 mProgressDrawable.setColorSchemeColors(getResources().getColor(R.color.accent_color));
345 mProgressDrawable.updateSizes(MaterialProgressDrawable.LARGE);
346 mLoadingIndicator.setImageDrawable(mProgressDrawable);
347
348 // We don't want to show the spinner every time we load an image if it loads quickly; instead,
349 // only start showing the spinner if loading the image has taken longer than half of a second.
350 mLoadingIndicator.postDelayed(new Runnable() {
351 @Override
352 public void run() {
Santiago Etchebehere41257a82018-03-30 10:50:12 -0700353 if (mFullResImageView != null && !mFullResImageView.hasImage()
354 && !mTestingModeEnabled) {
Jon Miranda16ea1b12017-12-12 14:52:48 -0800355 mLoadingIndicator.setVisibility(View.VISIBLE);
356 mLoadingIndicator.setAlpha(1f);
357 if (mProgressDrawable != null) {
358 mProgressDrawable.start();
359 }
360 }
361 }
362 }, 500);
363
364 mBottomSheetInitialState = (savedInstanceState == null)
365 ? BottomSheetBehavior.STATE_EXPANDED
Santiago Etchebehere41257a82018-03-30 10:50:12 -0700366 : savedInstanceState.getInt(KEY_BOTTOM_SHEET_STATE,
367 BottomSheetBehavior.STATE_EXPANDED);
Jon Miranda16ea1b12017-12-12 14:52:48 -0800368 setUpBottomSheetListeners();
369
370 return view;
371 }
372
373 @Override
374 public void onResume() {
375 super.onResume();
376
377 WallpaperPreferences preferences = InjectorProvider.getInjector().getPreferences(getActivity());
378 preferences.setLastAppActiveTimestamp(new Date().getTime());
379
380 // Show the staged 'load wallpaper' or 'set wallpaper' error dialog fragments if there is one
381 // that was unable to be shown earlier when this fragment's hosting activity didn't allow
382 // committing fragment transactions.
383 if (mStagedLoadWallpaperErrorDialogFragment != null) {
384 mStagedLoadWallpaperErrorDialogFragment.show(
385 getFragmentManager(), TAG_LOAD_WALLPAPER_ERROR_DIALOG_FRAGMENT);
386 mStagedLoadWallpaperErrorDialogFragment = null;
387 }
388 if (mStagedSetWallpaperErrorDialogFragment != null) {
389 mStagedSetWallpaperErrorDialogFragment.show(
390 getFragmentManager(), TAG_SET_WALLPAPER_ERROR_DIALOG_FRAGMENT);
391 mStagedSetWallpaperErrorDialogFragment = null;
392 }
393 }
394
395 @Override
396 public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
397 super.onCreateOptionsMenu(menu, inflater);
398 inflater.inflate(R.menu.preview_menu, menu);
399 }
400
401 @Override
402 public void onPrepareOptionsMenu(Menu menu) {
403 super.onPrepareOptionsMenu(menu);
404 MenuItem setWallpaperButton = menu.findItem(R.id.set_wallpaper);
405
406 if (mPreviewMode == MODE_CROP_AND_SET_WALLPAPER && isWallpaperLoaded()) {
407 setWallpaperButton.setVisible(true);
408 } else {
409 setWallpaperButton.setVisible(false);
410 }
411 }
412
413 @Override
414 public boolean onOptionsItemSelected(MenuItem item) {
415 int id = item.getItemId();
416 if (id == R.id.set_wallpaper) {
417 if (BuildCompat.isAtLeastN()) {
Santiago Etchebehere8f118fe2018-06-11 16:57:10 -0700418 requestDestination();
Jon Miranda16ea1b12017-12-12 14:52:48 -0800419 } else {
420 setCurrentWallpaper(WallpaperPersister.DEST_HOME_SCREEN);
421 }
422 return true;
423 } else if (id == android.R.id.home) {
424 // The Preview screen has multiple entry points. It could be opened from either
425 // the IndividualPreviewActivity, the "My photos" selection (by way of
426 // TopLevelPickerActivity), or from a system "crop and set wallpaper" intent.
427 // Therefore, handle the Up button as a global Back.
428 getActivity().onBackPressed();
429 return true;
430 }
431
432 return false;
433 }
434
Santiago Etchebehere8f118fe2018-06-11 16:57:10 -0700435 private void requestDestination() {
436 CurrentWallpaperInfoFactory factory = InjectorProvider.getInjector()
437 .getCurrentWallpaperFactory(getContext());
438
439 factory.createCurrentWallpaperInfos((homeWallpaper, lockWallpaper, presentationMode) -> {
440 SetWallpaperDialogFragment setWallpaperDialog = new SetWallpaperDialogFragment();
441 setWallpaperDialog.setTargetFragment(this, UNUSED_REQUEST_CODE);
442 if (homeWallpaper instanceof LiveWallpaperInfo && lockWallpaper == null) {
443 // if the lock wallpaper is a live wallpaper, we cannot set a home-only static one
444 setWallpaperDialog.setHomeOptionAvailable(false);
445 }
446 setWallpaperDialog.show(getFragmentManager(), TAG_SET_WALLPAPER_DIALOG_FRAGMENT);
447 }, true); // Force refresh as the wallpaper may have been set while this fragment was paused
448 }
449
Jon Miranda16ea1b12017-12-12 14:52:48 -0800450 @Override
451 public void onSetHomeScreen() {
452 setCurrentWallpaper(WallpaperPersister.DEST_HOME_SCREEN);
453 }
454
455 @Override
456 public void onSetLockScreen() {
457 setCurrentWallpaper(WallpaperPersister.DEST_LOCK_SCREEN);
458 }
459
460 @Override
461 public void onSetBoth() {
462 setCurrentWallpaper(WallpaperPersister.DEST_BOTH);
463 }
464
465 @Override
466 public void onClickTryAgain(@Destination int wallpaperDestination) {
467 setCurrentWallpaper(wallpaperDestination);
468 }
469
470 @Override
471 public void onClickOk() {
Santiago Etchebeheref790d702018-06-07 13:04:13 -0700472 FragmentActivity activity = getActivity();
473 if (activity != null) {
474 activity.finish();
475 }
Jon Miranda16ea1b12017-12-12 14:52:48 -0800476 }
477
478 @Override
479 public void onDestroy() {
480 super.onDestroy();
481 if (mProgressDialog != null) {
482 mProgressDialog.dismiss();
483 }
484 if (mProgressDrawable != null) {
485 mProgressDrawable.stop();
486 }
Santiago Etchebehere41257a82018-03-30 10:50:12 -0700487 mFullResImageView.recycle();
Jon Miranda16ea1b12017-12-12 14:52:48 -0800488 }
489
490 @Override
491 public void onSaveInstanceState(Bundle outState) {
492 super.onSaveInstanceState(outState);
493
494 final BottomSheetBehavior bottomSheetBehavior = BottomSheetBehavior.from(mBottomSheet);
495 outState.putInt(KEY_BOTTOM_SHEET_STATE, bottomSheetBehavior.getState());
496 }
497
498 private void updatePreviewPaneArrow(int bottomSheetState) {
499 if (bottomSheetState == BottomSheetBehavior.STATE_COLLAPSED) {
500 mPreviewPaneArrow.setImageResource(R.drawable.material_ic_keyboard_arrow_up_black_24);
501 mPreviewPaneArrow.setContentDescription(
502 getResources().getString(R.string.expand_attribution_panel));
503 } else if (bottomSheetState == BottomSheetBehavior.STATE_EXPANDED) {
504 mPreviewPaneArrow.setImageResource(R.drawable.material_ic_keyboard_arrow_down_black_24);
505 mPreviewPaneArrow.setContentDescription(
506 getResources().getString(R.string.collapse_attribution_panel));
507 }
508 mPreviewPaneArrow.setColorFilter(
509 getResources().getColor(R.color.preview_pane_arrow_color), Mode.SRC_IN);
510 }
511
Jon Miranda623e57e2018-01-05 16:33:30 -0800512 /**
513 * Returns a zoom level that is similar to the actual zoom, but that is exactly 0.5 ** n for some
514 * integer n. This is useful for downsampling a bitmap--we want to see the bitmap at full detail,
515 * or downsampled to 1 in every 2 pixels, or 1 in 4, and so on, depending on the zoom.
516 */
517 private static float getDownsampleZoom(float actualZoom) {
518 if (actualZoom > 1) {
519 // Very zoomed in, but we can't sample more than 1 pixel per pixel.
520 return 1.0f;
521 }
522 float lower = 1.0f / roundUpToPower2((int) Math.ceil(1 / actualZoom));
523 float upper = lower * 2;
524 return nearestValue(actualZoom, lower, upper);
525 }
526
527 /**
528 * Returns the integer rounded up to the next power of 2.
529 */
530 private static int roundUpToPower2(int value) {
531 return 1 << (32 - Integer.numberOfLeadingZeros(value - 1));
532 }
533
534 /**
535 * Returns the closer of two values a and b to the given value.
536 */
537 private static float nearestValue(float value, float a, float b) {
538 return Math.abs(a - value) < Math.abs(b - value) ? a : b;
539 }
540
Jon Miranda16ea1b12017-12-12 14:52:48 -0800541 private void setUpBottomSheetListeners() {
542 final BottomSheetBehavior bottomSheetBehavior = BottomSheetBehavior.from(mBottomSheet);
543
544 OnClickListener onClickListener = new OnClickListener() {
545 @Override
546 public void onClick(View view) {
547 if (bottomSheetBehavior.getState() == BottomSheetBehavior.STATE_COLLAPSED) {
548 bottomSheetBehavior.setState(BottomSheetBehavior.STATE_EXPANDED);
549 } else if (bottomSheetBehavior.getState() == BottomSheetBehavior.STATE_EXPANDED) {
550 bottomSheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
551 }
552 }
553 };
554 mAttributionTitle.setOnClickListener(onClickListener);
555 mPreviewPaneArrow.setOnClickListener(onClickListener);
556
557 bottomSheetBehavior.setBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() {
558 @Override
559 public void onStateChanged(View bottomSheet, int newState) {
560 // Don't respond to lingering state change events occurring after the fragment has already
561 // been detached from the activity. Else, IllegalStateException may occur when trying to
562 // fetch resources.
563 if (getActivity() == null) {
564 return;
565 }
566
567 updatePreviewPaneArrow(newState);
568 }
569
570 @Override
571 public void onSlide(View bottomSheet, float slideOffset) {
572 float alpha;
573 if (slideOffset >= 0) {
574 alpha = slideOffset;
575 } else {
576 alpha = 1f - slideOffset;
577 }
578 mAttributionTitle.setAlpha(alpha);
579 mAttributionSubtitle1.setAlpha(alpha);
580 mAttributionSubtitle2.setAlpha(alpha);
581 mAttributionExploreButton.setAlpha(alpha);
582 }
583 });
584 }
585
586 private boolean isWallpaperLoaded() {
Santiago Etchebehere41257a82018-03-30 10:50:12 -0700587 return mFullResImageView.hasImage();
Jon Miranda16ea1b12017-12-12 14:52:48 -0800588 }
589
590 private void populateAttributionPane() {
591 final Context context = getContext();
592
593 final BottomSheetBehavior bottomSheetBehavior = BottomSheetBehavior.from(mBottomSheet);
594
595 List<String> attributions = mWallpaper.getAttributions(context);
596 if (attributions.size() > 0 && attributions.get(0) != null) {
597 mAttributionTitle.setText(attributions.get(0));
598 }
599
600 if (attributions.size() > 1 && attributions.get(1) != null) {
601 mAttributionSubtitle1.setVisibility(View.VISIBLE);
602 mAttributionSubtitle1.setText(attributions.get(1));
603 }
604
605 if (attributions.size() > 2 && attributions.get(2) != null) {
606 mAttributionSubtitle2.setVisibility(View.VISIBLE);
607 mAttributionSubtitle2.setText(attributions.get(2));
608 }
609
610 String actionUrl = mWallpaper.getActionUrl(context);
611 if (actionUrl != null && !actionUrl.isEmpty()) {
612 if (mExploreIntent != null) {
613 if (Flags.skipDailyWallpaperButtonEnabled) {
Santiago Etchebehered24506c2018-04-05 17:02:42 -0700614 Drawable exploreButtonDrawable = context.getDrawable(
Santiago Etchebeheree0810d02018-05-10 17:39:40 -0700615 mWallpaper.getActionIconRes(context));
Jon Miranda16ea1b12017-12-12 14:52:48 -0800616
617 // This Drawable's state is shared across the app, so make a copy of it before applying a
618 // color tint as not to affect other clients elsewhere in the app.
Santiago Etchebehered1bd5092018-04-18 16:03:30 -0700619 exploreButtonDrawable = exploreButtonDrawable.getConstantState()
620 .newDrawable().mutate();
Jon Miranda16ea1b12017-12-12 14:52:48 -0800621 // Color the "compass" icon with the accent color.
622 exploreButtonDrawable.setColorFilter(
623 getResources().getColor(R.color.accent_color), Mode.SRC_IN);
624 ButtonDrawableSetterCompat.setDrawableToButtonStart(
625 mAttributionExploreButton, exploreButtonDrawable);
Santiago Etchebehered1bd5092018-04-18 16:03:30 -0700626 mAttributionExploreButton.setText(context.getString(
Santiago Etchebeheree0810d02018-05-10 17:39:40 -0700627 mWallpaper.getActionLabelRes(context)));
Jon Miranda16ea1b12017-12-12 14:52:48 -0800628 }
629
630 mAttributionExploreSection.setVisibility(View.VISIBLE);
631 mAttributionExploreButton.setOnClickListener(new OnClickListener() {
632 @Override
633 public void onClick(View view) {
Santiago Etchebeherece5613f2018-06-01 13:22:47 -0700634 mUserEventLogger.logActionClicked(mWallpaper.getCollectionId(context),
635 mWallpaper.getActionLabelRes(context));
Jon Miranda16ea1b12017-12-12 14:52:48 -0800636
637 startActivity(mExploreIntent);
638 }
639 });
640 }
641 }
642
643 mBottomSheet.setVisibility(View.VISIBLE);
644
645 // Initialize the state of the BottomSheet based on the current state because if the initial
646 // and current state are the same, the state change listener won't fire and set the correct
647 // arrow asset and text alpha.
648 if (bottomSheetBehavior.getState() == BottomSheetBehavior.STATE_EXPANDED) {
649 updatePreviewPaneArrow(BottomSheetBehavior.STATE_EXPANDED);
650 } else {
651 mAttributionTitle.setAlpha(0f);
652 mAttributionSubtitle1.setAlpha(0f);
653 mAttributionSubtitle2.setAlpha(0f);
654 }
655
656 // Let the state change listener take care of animating a state change to the initial state if
657 // there's a state change.
658 bottomSheetBehavior.setState(mBottomSheetInitialState);
659 }
660
661 /**
662 * Initializes MosaicView by initializing tiling, setting a fallback page bitmap, and initializing
663 * a zoom-scroll observer and click listener.
664 */
Santiago Etchebehere41257a82018-03-30 10:50:12 -0700665 private void initFullResView() {
666 mFullResImageView.setMinimumScaleType(SubsamplingScaleImageView.SCALE_TYPE_CENTER_CROP);
Jon Miranda623e57e2018-01-05 16:33:30 -0800667
668 // Set a solid black "page bitmap" so MosaicView draws a black background while waiting
669 // for the image to load or a transparent one if a thumbnail already loaded.
670 Bitmap blackBitmap = Bitmap.createBitmap(1, 1, Config.ARGB_8888);
671 int color = (mLowResImageView.getDrawable() == null) ? Color.BLACK : Color.TRANSPARENT;
672 blackBitmap.setPixel(0, 0, color);
Santiago Etchebehere41257a82018-03-30 10:50:12 -0700673 mFullResImageView.setImage(ImageSource.bitmap(blackBitmap));
Jon Miranda623e57e2018-01-05 16:33:30 -0800674
Jon Miranda16ea1b12017-12-12 14:52:48 -0800675 // Then set a fallback "page bitmap" to cover the whole MosaicView, which is an actual
676 // (lower res) version of the image to be displayed.
Jon Miranda623e57e2018-01-05 16:33:30 -0800677 Point targetPageBitmapSize = new Point(mRawWallpaperSize);
Jon Miranda16ea1b12017-12-12 14:52:48 -0800678 mWallpaperAsset.decodeBitmap(targetPageBitmapSize.x, targetPageBitmapSize.y,
679 new BitmapReceiver() {
680 @Override
681 public void onBitmapDecoded(Bitmap pageBitmap) {
682 // Check that the activity is still around since the decoding task started.
683 if (getActivity() == null) {
684 return;
685 }
686
687 // Some of these may be null depending on if the Fragment is paused, stopped,
688 // or destroyed.
689 if (mLoadingIndicator != null) {
690 mLoadingIndicator.setVisibility(View.GONE);
691 }
692 // The page bitmap may be null if there was a decoding error, so show an error dialog.
693 if (pageBitmap == null) {
694 showLoadWallpaperErrorDialog();
695 return;
696 }
Santiago Etchebehere41257a82018-03-30 10:50:12 -0700697 if (mFullResImageView != null) {
Jon Miranda623e57e2018-01-05 16:33:30 -0800698 // Set page bitmap.
Santiago Etchebehere41257a82018-03-30 10:50:12 -0700699 mFullResImageView.setImage(ImageSource.bitmap(pageBitmap));
Jon Miranda16ea1b12017-12-12 14:52:48 -0800700
Santiago Etchebehere6af67ed2018-03-27 16:37:57 -0700701 setDefaultWallpaperZoomAndScroll();
Jon Miranda623e57e2018-01-05 16:33:30 -0800702 crossFadeInMosaicView();
Jon Miranda16ea1b12017-12-12 14:52:48 -0800703
Jon Miranda623e57e2018-01-05 16:33:30 -0800704 // Record memory snapshot of app one second delayed to allow time for MosaicView tiles
705 // to be decoded and overlaid on top of the page bitmap.
706 new Handler().postDelayed(new Runnable() {
707 @Override
708 public void run() {
709 if (getActivity() == null) {
710 return;
711 }
Jon Miranda16ea1b12017-12-12 14:52:48 -0800712
Jon Miranda623e57e2018-01-05 16:33:30 -0800713 InjectorProvider.getInjector().getPerformanceMonitor()
714 .recordFullResPreviewLoadedMemorySnapshot();
Jon Miranda16ea1b12017-12-12 14:52:48 -0800715 }
Jon Miranda623e57e2018-01-05 16:33:30 -0800716 }, 1000);
717 }
Jon Miranda16ea1b12017-12-12 14:52:48 -0800718 if (mProgressDrawable != null) {
719 mProgressDrawable.stop();
720 }
Jon Miranda16ea1b12017-12-12 14:52:48 -0800721 getActivity().invalidateOptionsMenu();
722
723 populateAttributionPane();
724 }
725 });
726 }
727
728 /**
729 * Makes the MosaicView visible with an alpha fade-in animation while fading out the loading
730 * indicator.
731 */
732 private void crossFadeInMosaicView() {
733 long shortAnimationDuration = getResources().getInteger(android.R.integer.config_shortAnimTime);
734
Santiago Etchebehere41257a82018-03-30 10:50:12 -0700735 mFullResImageView.setAlpha(0f);
736 mFullResImageView.animate()
Jon Miranda16ea1b12017-12-12 14:52:48 -0800737 .alpha(1f)
Jon Miranda623e57e2018-01-05 16:33:30 -0800738 .setDuration(shortAnimationDuration)
739 .setListener(new AnimatorListenerAdapter() {
740 @Override
741 public void onAnimationEnd(Animator animation) {
742 // Clear the thumbnail bitmap reference to save memory since it's no longer visible.
743 if (mLowResImageView != null) {
744 mLowResImageView.setImageBitmap(null);
745 }
746 }
747 });
Jon Miranda16ea1b12017-12-12 14:52:48 -0800748
749 mLoadingIndicator.animate()
750 .alpha(0f)
751 .setDuration(shortAnimationDuration)
752 .setListener(new AnimatorListenerAdapter() {
753 @Override
754 public void onAnimationEnd(Animator animation) {
755 if (mLoadingIndicator != null) {
756 mLoadingIndicator.setVisibility(View.GONE);
757 }
758 }
759 });
760 }
761
Santiago Etchebehere6af67ed2018-03-27 16:37:57 -0700762 /**
763 * Sets the default wallpaper zoom and scroll position based on a "crop surface"
764 * (with extra width to account for parallax) superimposed on the screen. Shows as much of the
765 * wallpaper as possible on the crop surface and align screen to crop surface such that the
766 * default preview matches what would be seen by the user in the left-most home screen.
767 *
768 * <p>This method is called once in the Fragment lifecycle after the wallpaper asset has loaded
769 * and rendered to the layout.
770 */
771 private void setDefaultWallpaperZoomAndScroll() {
772 // Determine minimum zoom to fit maximum visible area of wallpaper on crop surface.
773 float defaultWallpaperZoom =
774 WallpaperCropUtils.calculateMinZoom(mRawWallpaperSize, mDefaultCropSurfaceSize);
775 float minWallpaperZoom =
776 WallpaperCropUtils.calculateMinZoom(mRawWallpaperSize, mScreenSize);
777
778 Point screenToCropSurfacePosition = WallpaperCropUtils.calculateCenterPosition(
779 mDefaultCropSurfaceSize, mScreenSize, true /* alignStart */, isRtl());
780 Point zoomedWallpaperSize = new Point(
781 Math.round(mRawWallpaperSize.x * defaultWallpaperZoom),
782 Math.round(mRawWallpaperSize.y * defaultWallpaperZoom));
783 Point cropSurfaceToWallpaperPosition = WallpaperCropUtils.calculateCenterPosition(
784 zoomedWallpaperSize, mDefaultCropSurfaceSize, false /* alignStart */, isRtl());
785
786 // Set min wallpaper zoom and max zoom on MosaicView widget.
Santiago Etchebehere41257a82018-03-30 10:50:12 -0700787 mFullResImageView.setMaxScale(Math.max(DEFAULT_WALLPAPER_MAX_ZOOM, defaultWallpaperZoom));
788 mFullResImageView.setMinScale(minWallpaperZoom);
Santiago Etchebehere6af67ed2018-03-27 16:37:57 -0700789
790 // Set center to composite positioning between scaled wallpaper and screen.
791 PointF centerPosition = new PointF(
792 mRawWallpaperSize.x / 2f,
793 mRawWallpaperSize.y / 2f);
794 centerPosition.offset( - (screenToCropSurfacePosition.x + cropSurfaceToWallpaperPosition.x),
795 - (screenToCropSurfacePosition.y + cropSurfaceToWallpaperPosition.y));
796
Santiago Etchebehere41257a82018-03-30 10:50:12 -0700797 mFullResImageView.setScaleAndCenter(defaultWallpaperZoom, centerPosition);
Santiago Etchebehere6af67ed2018-03-27 16:37:57 -0700798 }
799
Clément Julliardea1638d2018-05-21 19:15:17 -0700800 protected Rect calculateCropRect() {
Jon Miranda16ea1b12017-12-12 14:52:48 -0800801 // Calculate Rect of wallpaper in physical pixel terms (i.e., scaled to current zoom).
Santiago Etchebehere41257a82018-03-30 10:50:12 -0700802 float wallpaperZoom = mFullResImageView.getScale();
Jon Miranda16ea1b12017-12-12 14:52:48 -0800803 int scaledWallpaperWidth = (int) (mRawWallpaperSize.x * wallpaperZoom);
804 int scaledWallpaperHeight = (int) (mRawWallpaperSize.y * wallpaperZoom);
Jon Miranda623e57e2018-01-05 16:33:30 -0800805 Rect rect = new Rect();
Santiago Etchebehere41257a82018-03-30 10:50:12 -0700806 mFullResImageView.visibleFileRect(rect);
Jon Miranda623e57e2018-01-05 16:33:30 -0800807 int scrollX = (int) (rect.left * wallpaperZoom);
808 int scrollY = (int) (rect.top * wallpaperZoom);
Jon Miranda16ea1b12017-12-12 14:52:48 -0800809
Jon Miranda623e57e2018-01-05 16:33:30 -0800810 rect.set(0, 0, scaledWallpaperWidth, scaledWallpaperHeight);
Jon Miranda16ea1b12017-12-12 14:52:48 -0800811 Point screenSize = ScreenSizeCalculator.getInstance().getScreenSize(
812 getActivity().getWindowManager().getDefaultDisplay());
Jon Miranda16ea1b12017-12-12 14:52:48 -0800813 // Crop rect should start off as the visible screen and then include extra width and height if
814 // available within wallpaper at the current zoom.
815 Rect cropRect = new Rect(scrollX, scrollY, scrollX + screenSize.x, scrollY + screenSize.y);
816
817 Point defaultCropSurfaceSize = WallpaperCropUtils.getDefaultCropSurfaceSize(
818 getResources(), getActivity().getWindowManager().getDefaultDisplay());
819 int extraWidth = defaultCropSurfaceSize.x - screenSize.x;
820 int extraHeightTopAndBottom = (int) ((defaultCropSurfaceSize.y - screenSize.y) / 2f);
821
822 // Try to increase size of screenRect to include extra width depending on the layout direction.
823 if (isRtl()) {
Jon Miranda623e57e2018-01-05 16:33:30 -0800824 cropRect.left = Math.max(cropRect.left - extraWidth, rect.left);
Jon Miranda16ea1b12017-12-12 14:52:48 -0800825 } else {
Jon Miranda623e57e2018-01-05 16:33:30 -0800826 cropRect.right = Math.min(cropRect.right + extraWidth, rect.right);
Jon Miranda16ea1b12017-12-12 14:52:48 -0800827 }
828
829 // Try to increase the size of the cropRect to to include extra height.
830 int availableExtraHeightTop = cropRect.top - Math.max(
Jon Miranda623e57e2018-01-05 16:33:30 -0800831 rect.top,
Jon Miranda16ea1b12017-12-12 14:52:48 -0800832 cropRect.top - extraHeightTopAndBottom);
833 int availableExtraHeightBottom = Math.min(
Jon Miranda623e57e2018-01-05 16:33:30 -0800834 rect.bottom,
Jon Miranda16ea1b12017-12-12 14:52:48 -0800835 cropRect.bottom + extraHeightTopAndBottom) - cropRect.bottom;
836
837 int availableExtraHeightTopAndBottom =
838 Math.min(availableExtraHeightTop, availableExtraHeightBottom);
839 cropRect.top -= availableExtraHeightTopAndBottom;
840 cropRect.bottom += availableExtraHeightTopAndBottom;
841
842 return cropRect;
843 }
844
845 /**
846 * Sets current wallpaper to the device based on current zoom and scroll state.
847 *
848 * @param destination The wallpaper destination i.e. home vs. lockscreen vs. both.
849 */
850 private void setCurrentWallpaper(@Destination final int destination) {
851 mPreferences.setPendingWallpaperSetStatus(WallpaperPreferences.WALLPAPER_SET_PENDING);
852
853 // Save current screen rotation so we can temporarily disable rotation while setting the
854 // wallpaper and restore after setting the wallpaper finishes.
855 saveAndLockScreenOrientation();
856
Jon Miranda623e57e2018-01-05 16:33:30 -0800857 // Clear MosaicView tiles and Glide's cache and pools to reclaim memory for final cropped
858 // bitmap.
Jon Miranda16ea1b12017-12-12 14:52:48 -0800859 Glide.get(getActivity()).clearMemory();
860
861 // ProgressDialog endlessly updates the UI thread, keeping it from going idle which therefore
862 // causes Espresso to hang once the dialog is shown.
863 if (!mTestingModeEnabled) {
864 int themeResId;
865 if (VERSION.SDK_INT < VERSION_CODES.LOLLIPOP) {
866 themeResId = R.style.ProgressDialogThemePreL;
867 } else {
868 themeResId = R.style.LightDialogTheme;
869 }
870 mProgressDialog = new ProgressDialog(getActivity(), themeResId);
871
872 mProgressDialog.setTitle(PROGRESS_DIALOG_NO_TITLE);
873 mProgressDialog.setMessage(
874 getResources().getString(R.string.set_wallpaper_progress_message));
875 mProgressDialog.setIndeterminate(PROGRESS_DIALOG_INDETERMINATE);
876 mProgressDialog.show();
877 }
878
Santiago Etchebehere41257a82018-03-30 10:50:12 -0700879 float wallpaperScale = mFullResImageView.getScale();
Jon Miranda16ea1b12017-12-12 14:52:48 -0800880 Rect cropRect = calculateCropRect();
Jon Miranda16ea1b12017-12-12 14:52:48 -0800881 mWallpaperPersister.setIndividualWallpaper(mWallpaper, mWallpaperAsset, cropRect,
882 wallpaperScale, destination, new SetWallpaperCallback() {
883 @Override
884 public void onSuccess() {
885 Context context = getContext();
886 mUserEventLogger.logWallpaperSet(
887 mWallpaper.getCollectionId(context),
888 mWallpaper.getWallpaperId());
889 mPreferences.setPendingWallpaperSetStatus(
890 WallpaperPreferences.WALLPAPER_SET_NOT_PENDING);
891 mUserEventLogger.logWallpaperSetResult(
892 UserEventLogger.WALLPAPER_SET_RESULT_SUCCESS);
893
894 if (getActivity() == null) {
895 return;
896 }
897
898 if (mProgressDialog != null) {
899 mProgressDialog.dismiss();
900 }
901
902 restoreScreenOrientation();
903 finishActivityWithResultOk();
904 }
905
906 @Override
907 public void onError(Throwable throwable) {
908 mPreferences.setPendingWallpaperSetStatus(
909 WallpaperPreferences.WALLPAPER_SET_NOT_PENDING);
910 mUserEventLogger.logWallpaperSetResult(
911 UserEventLogger.WALLPAPER_SET_RESULT_FAILURE);
912 @WallpaperSetFailureReason int failureReason = ThrowableAnalyzer.isOOM(throwable)
913 ? UserEventLogger.WALLPAPER_SET_FAILURE_REASON_OOM
914 : UserEventLogger.WALLPAPER_SET_FAILURE_REASON_OTHER;
915 mUserEventLogger.logWallpaperSetFailureReason(failureReason);
916
917 if (getActivity() == null) {
918 return;
919 }
920
921 if (mProgressDialog != null) {
922 mProgressDialog.dismiss();
923 }
924 restoreScreenOrientation();
925 showSetWallpaperErrorDialog(destination);
926 }
927 });
928 }
929
930 private void finishActivityWithResultOk() {
931 try {
932 Toast.makeText(
933 getActivity(), R.string.wallpaper_set_successfully_message, Toast.LENGTH_SHORT).show();
934 } catch (NotFoundException e) {
935 Log.e(TAG, "Could not show toast " + e);
936 }
937 getActivity().overridePendingTransition(R.anim.fade_in, R.anim.fade_out);
938 getActivity().setResult(Activity.RESULT_OK);
939 getActivity().finish();
940 }
941
942 private void showSetWallpaperErrorDialog(@Destination int wallpaperDestination) {
943 SetWallpaperErrorDialogFragment newFragment = SetWallpaperErrorDialogFragment.newInstance(
944 R.string.set_wallpaper_error_message, wallpaperDestination);
945 newFragment.setTargetFragment(this, UNUSED_REQUEST_CODE);
946
947 // Show 'set wallpaper' error dialog now if it's safe to commit fragment transactions, otherwise
948 // stage it for later when the hosting activity is in a state to commit fragment transactions.
949 BasePreviewActivity activity = (BasePreviewActivity) getActivity();
950 if (activity.isSafeToCommitFragmentTransaction()) {
951 newFragment.show(getFragmentManager(), TAG_SET_WALLPAPER_ERROR_DIALOG_FRAGMENT);
952 } else {
953 mStagedSetWallpaperErrorDialogFragment = newFragment;
954 }
955 }
956
957 /**
958 * Shows 'load wallpaper' error dialog now or stage it to be shown when the hosting activity is in
959 * a state that allows committing fragment transactions.
960 */
961 private void showLoadWallpaperErrorDialog() {
962 LoadWallpaperErrorDialogFragment dialogFragment =
963 LoadWallpaperErrorDialogFragment.newInstance();
964 dialogFragment.setTargetFragment(PreviewFragment.this, UNUSED_REQUEST_CODE);
965
966 // Show 'load wallpaper' error dialog now or stage it to be shown when the hosting
967 // activity is in a state that allows committing fragment transactions.
968 BasePreviewActivity activity = (BasePreviewActivity) getActivity();
969 if (activity != null && activity.isSafeToCommitFragmentTransaction()) {
970 dialogFragment.show(PreviewFragment.this.getFragmentManager(),
971 TAG_LOAD_WALLPAPER_ERROR_DIALOG_FRAGMENT);
972 } else {
973 mStagedLoadWallpaperErrorDialogFragment = dialogFragment;
974 }
975 }
976
Jon Miranda623e57e2018-01-05 16:33:30 -0800977 @IntDef({
978 ActivityInfo.SCREEN_ORIENTATION_PORTRAIT,
979 ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE,
980 ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT,
981 ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE})
982 private @interface ActivityInfoScreenOrientation {
983 }
984
Jon Miranda16ea1b12017-12-12 14:52:48 -0800985 /**
986 * Gets the appropriate ActivityInfo orientation for the current configuration orientation to
987 * enable locking screen rotation at API levels lower than 18.
988 */
989 @ActivityInfoScreenOrientation
990 private int getCompatActivityInfoOrientation() {
991 int configOrientation = getResources().getConfiguration().orientation;
992 final Display display = getActivity().getWindowManager().getDefaultDisplay();
993 int naturalOrientation = Configuration.ORIENTATION_LANDSCAPE;
994 switch (display.getRotation()) {
995 case Surface.ROTATION_0:
996 case Surface.ROTATION_180:
997 // We are currently in the same basic orientation as the natural orientation.
998 naturalOrientation = configOrientation;
999 break;
1000 case Surface.ROTATION_90:
1001 case Surface.ROTATION_270:
1002 // We are currently in the other basic orientation to the natural orientation.
1003 naturalOrientation = (configOrientation == Configuration.ORIENTATION_LANDSCAPE)
1004 ? Configuration.ORIENTATION_PORTRAIT : Configuration.ORIENTATION_LANDSCAPE;
1005 break;
1006 default:
1007 // continue below
1008 }
1009
1010 // Since the map starts at portrait, we need to offset if this device's natural orientation
1011 // is landscape.
1012 int indexOffset = 0;
1013 if (naturalOrientation == Configuration.ORIENTATION_LANDSCAPE) {
1014 indexOffset = 1;
1015 }
1016
1017 switch ((display.getRotation() + indexOffset) % 4) {
1018 case 0:
1019 return ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
1020 case 1:
1021 return ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
1022 case 2:
1023 return ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT;
1024 case 3:
1025 return ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE;
1026 default:
1027 Log.e(TAG, "Display rotation did not correspond to a valid ActivityInfo orientation with"
1028 + " display rotation: " + display.getRotation() + " and index offset: " + indexOffset
1029 + ".");
1030 return ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
1031 }
1032 }
1033
Jon Miranda623e57e2018-01-05 16:33:30 -08001034 @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
Jon Miranda16ea1b12017-12-12 14:52:48 -08001035 private void saveAndLockScreenOrientation() {
1036 mCurrentScreenOrientation = getActivity().getRequestedOrientation();
1037 if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN_MR2) {
1038 getActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LOCKED);
1039 } else {
1040 getActivity().setRequestedOrientation(getCompatActivityInfoOrientation());
1041 }
1042 }
1043
1044 private void restoreScreenOrientation() {
1045 getActivity().setRequestedOrientation(mCurrentScreenOrientation);
1046 }
1047
1048 /**
1049 * Returns whether layout direction is RTL (or false for LTR). Since native RTL layout support was
1050 * added in API 17, returns false for versions lower than 17.
1051 */
1052 private boolean isRtl() {
Jon Miranda623e57e2018-01-05 16:33:30 -08001053 return VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1
Jon Miranda16ea1b12017-12-12 14:52:48 -08001054 && getResources().getConfiguration().getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
1055 }
Jon Miranda623e57e2018-01-05 16:33:30 -08001056}