1 /*
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  */
16 package com.android.wallpaper.picker;
17 
18 import android.app.Activity;
19 import android.app.ProgressDialog;
20 import android.content.Context;
21 import android.content.Intent;
22 import android.graphics.Color;
23 import android.graphics.Insets;
24 import android.graphics.Point;
25 import android.graphics.PorterDuff.Mode;
26 import android.graphics.drawable.Drawable;
27 import android.net.Uri;
28 import android.os.AsyncTask;
29 import android.os.Build.VERSION;
30 import android.os.Build.VERSION_CODES;
31 import android.os.Bundle;
32 import android.provider.Settings;
33 import android.util.Log;
34 import android.view.View;
35 import android.view.View.OnClickListener;
36 import android.view.WindowInsets;
37 import android.widget.Button;
38 import android.widget.FrameLayout;
39 import android.widget.ImageView;
40 import android.widget.LinearLayout;
41 import android.widget.TextView;
42 
43 import androidx.annotation.NonNull;
44 import androidx.annotation.Nullable;
45 import androidx.appcompat.app.AlertDialog;
46 import androidx.appcompat.widget.Toolbar;
47 import androidx.fragment.app.Fragment;
48 import androidx.fragment.app.FragmentManager;
49 
50 import com.android.wallpaper.R;
51 import com.android.wallpaper.asset.Asset;
52 import com.android.wallpaper.compat.BuildCompat;
53 import com.android.wallpaper.compat.ButtonDrawableSetterCompat;
54 import com.android.wallpaper.config.Flags;
55 import com.android.wallpaper.model.Category;
56 import com.android.wallpaper.model.ImageWallpaperInfo;
57 import com.android.wallpaper.model.WallpaperInfo;
58 import com.android.wallpaper.module.CurrentWallpaperInfoFactory;
59 import com.android.wallpaper.module.CurrentWallpaperInfoFactory.WallpaperInfoCallback;
60 import com.android.wallpaper.module.DailyLoggingAlarmScheduler;
61 import com.android.wallpaper.module.ExploreIntentChecker;
62 import com.android.wallpaper.module.FormFactorChecker;
63 import com.android.wallpaper.module.Injector;
64 import com.android.wallpaper.module.InjectorProvider;
65 import com.android.wallpaper.module.NetworkStatusNotifier;
66 import com.android.wallpaper.module.NetworkStatusNotifier.NetworkStatus;
67 import com.android.wallpaper.module.UserEventLogger;
68 import com.android.wallpaper.module.UserEventLogger.WallpaperSetFailureReason;
69 import com.android.wallpaper.module.WallpaperPersister;
70 import com.android.wallpaper.module.WallpaperPersister.Destination;
71 import com.android.wallpaper.module.WallpaperPersister.SetWallpaperCallback;
72 import com.android.wallpaper.module.WallpaperPersister.WallpaperPosition;
73 import com.android.wallpaper.module.WallpaperPreferences;
74 import com.android.wallpaper.module.WallpaperPreferences.PresentationMode;
75 import com.android.wallpaper.module.WallpaperRotationRefresher;
76 import com.android.wallpaper.module.WallpaperRotationRefresher.Listener;
77 import com.android.wallpaper.picker.CategoryFragment.CategoryFragmentHost;
78 import com.android.wallpaper.picker.WallpaperDisabledFragment.WallpaperSupportLevel;
79 import com.android.wallpaper.picker.individual.IndividualPickerFragment;
80 import com.android.wallpaper.util.ScreenSizeCalculator;
81 import com.android.wallpaper.util.ThrowableAnalyzer;
82 
83 import com.google.android.material.bottomsheet.BottomSheetBehavior;
84 import com.google.android.material.bottomsheet.BottomSheetBehavior.BottomSheetCallback;
85 import com.google.android.material.tabs.TabLayout;
86 import com.google.android.material.tabs.TabLayout.OnTabSelectedListener;
87 import com.google.android.material.tabs.TabLayout.Tab;
88 
89 import java.util.List;
90 
91 /**
92  * Activity allowing users to select a category of wallpapers to choose from.
93  */
94 public class TopLevelPickerActivity extends BaseActivity implements WallpapersUiContainer,
95         CurrentWallpaperBottomSheetPresenter, SetWallpaperErrorDialogFragment.Listener,
96         MyPhotosStarter, CategoryFragmentHost {
97 
98     private static final String TAG_SET_WALLPAPER_ERROR_DIALOG_FRAGMENT =
99             "toplevel_set_wallpaper_error_dialog";
100 
101     private static final String TAG = "TopLevelPicker";
102     private static final String KEY_SELECTED_CATEGORY_TAB = "selected_category_tab";
103 
104     private WallpaperPickerDelegate mDelegate;
105     private int mLastSelectedCategoryTabIndex;
106     private UserEventLogger mUserEventLogger;
107     private NetworkStatusNotifier mNetworkStatusNotifier;
108     private NetworkStatusNotifier.Listener mNetworkStatusListener;
109     private WallpaperPersister mWallpaperPersister;
110     private boolean mWasCustomPhotoWallpaperSet;
111     @WallpaperPosition
112     private int mCustomPhotoWallpaperPosition;
113 
114     /**
115      * Progress dialogs for "refresh daily wallpaper" and "set wallpaper" operations.
116      */
117     private ProgressDialog mRefreshWallpaperProgressDialog;
118     private ProgressDialog mSetWallpaperProgressDialog;
119 
120     /**
121      * Designates a test mode of operation -- in which certain UI features are disabled to allow for
122      * UI tests to run correctly.
123      */
124     private boolean mTestingMode;
125 
126     /**
127      * UI for the "currently set wallpaper" BottomSheet.
128      */
129     private LinearLayout mBottomSheet;
130     private ImageView mCurrentWallpaperImage;
131     private TextView mCurrentWallpaperPresentationMode;
132     private TextView mCurrentWallpaperTitle;
133     private TextView mCurrentWallpaperSubtitle;
134     private Button mCurrentWallpaperExploreButton;
135     private Button mCurrentWallpaperSkipWallpaperButton;
136     private FrameLayout mFragmentContainer;
137     private FrameLayout mLoadingIndicatorContainer;
138     private LinearLayout mWallpaperPositionOptions;
139 
140     /**
141      * Staged error dialog fragments that were unable to be shown when the activity didn't allow
142      * committing fragment transactions.
143      */
144     private SetWallpaperErrorDialogFragment mStagedSetWallpaperErrorDialogFragment;
145 
146     /**
147      * A wallpaper pending set to the device--we retain a reference to this in order to facilitate
148      * retry or re-crop operations.
149      */
150     private WallpaperInfo mPendingSetWallpaperInfo;
151 
getTextColorIdForWallpaperPositionButton(boolean isSelected)152     private static int getTextColorIdForWallpaperPositionButton(boolean isSelected) {
153         return isSelected ? R.color.accent_color : R.color.material_grey500;
154     }
155 
156     @Override
onCreate(Bundle savedInstanceState)157     protected void onCreate(Bundle savedInstanceState) {
158         super.onCreate(savedInstanceState);
159 
160         mLastSelectedCategoryTabIndex = -1;
161 
162         Injector injector = InjectorProvider.getInjector();
163         mDelegate = new WallpaperPickerDelegate(this, this, injector);
164         mUserEventLogger = injector.getUserEventLogger(this);
165         mNetworkStatusNotifier = injector.getNetworkStatusNotifier(this);
166         mWallpaperPersister = injector.getWallpaperPersister(this);
167         mWasCustomPhotoWallpaperSet = false;
168 
169         @WallpaperSupportLevel int wallpaperSupportLevel = mDelegate.getWallpaperSupportLevel();
170         if (wallpaperSupportLevel != WallpaperDisabledFragment.SUPPORTED_CAN_SET) {
171             setContentView(R.layout.activity_top_level_picker);
172 
173             FragmentManager fm = getSupportFragmentManager();
174             WallpaperDisabledFragment wallpaperDisabledFragment =
175                     WallpaperDisabledFragment.newInstance(wallpaperSupportLevel);
176             fm.beginTransaction()
177                     .add(R.id.fragment_container, wallpaperDisabledFragment)
178                     .commit();
179             return;
180         }
181 
182         if (mDelegate.getFormFactor() == FormFactorChecker.FORM_FACTOR_MOBILE) {
183             initializeMobile(true /* shouldForceRefresh */);
184         } else { // DESKTOP
185             initializeDesktop(savedInstanceState);
186         }
187     }
188 
189     @Override
onResume()190     protected void onResume() {
191         super.onResume();
192         boolean provisioned = Settings.Global.getInt(getContentResolver(),
193                 Settings.Global.DEVICE_PROVISIONED, 0) != 0;
194 
195         mUserEventLogger.logResumed(provisioned, true);
196         // Show the staged 'load wallpaper' or 'set wallpaper' error dialog fragments if there is one
197         // that was unable to be shown earlier when this fragment's hosting activity didn't allow
198         // committing fragment transactions.
199         if (mStagedSetWallpaperErrorDialogFragment != null) {
200             mStagedSetWallpaperErrorDialogFragment.show(
201                     getSupportFragmentManager(), TAG_SET_WALLPAPER_ERROR_DIALOG_FRAGMENT);
202             mStagedSetWallpaperErrorDialogFragment = null;
203         }
204     }
205 
206     @Override
onStop()207     protected void onStop() {
208         mUserEventLogger.logStopped();
209         super.onStop();
210     }
211 
212     @Override
onDestroy()213     protected void onDestroy() {
214         super.onDestroy();
215         mDelegate.cleanUp();
216         if (mNetworkStatusListener != null) {
217             mNetworkStatusNotifier.unregisterListener(mNetworkStatusListener);
218         }
219 
220         if (mRefreshWallpaperProgressDialog != null) {
221             mRefreshWallpaperProgressDialog.dismiss();
222         }
223         if (mSetWallpaperProgressDialog != null) {
224             mSetWallpaperProgressDialog.dismiss();
225         }
226     }
227 
228     @Override
onBackPressed()229     public void onBackPressed() {
230         CategoryFragment categoryFragment = getCategoryFragment();
231         if (categoryFragment != null && categoryFragment.popChildFragment()) {
232             return;
233         }
234         super.onBackPressed();
235     }
236 
237     @Override
requestCustomPhotoPicker(PermissionChangedListener listener)238     public void requestCustomPhotoPicker(PermissionChangedListener listener) {
239         mDelegate.requestCustomPhotoPicker(listener);
240     }
241 
242     @Override
requestExternalStoragePermission(PermissionChangedListener listener)243     public void requestExternalStoragePermission(PermissionChangedListener listener) {
244         mDelegate.requestExternalStoragePermission(listener);
245     }
246 
247     /**
248      * Returns whether READ_EXTERNAL_STORAGE has been granted for the application.
249      */
isReadExternalStoragePermissionGranted()250     public boolean isReadExternalStoragePermissionGranted() {
251         return mDelegate.isReadExternalStoragePermissionGranted();
252     }
253 
initializeMobile(boolean shouldForceRefresh)254     private void initializeMobile(boolean shouldForceRefresh) {
255         setContentView(R.layout.activity_top_level_picker);
256         getWindow().getDecorView().setSystemUiVisibility(
257                 getWindow().getDecorView().getSystemUiVisibility()
258                         | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
259                         | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
260         findViewById(R.id.fragment_container)
261                 .setOnApplyWindowInsetsListener((view, windowInsets) -> {
262             view.setPadding(view.getPaddingLeft(), windowInsets.getSystemWindowInsetTop(),
263                     view.getPaddingRight(), view.getPaddingBottom());
264             // Consume only the top inset (status bar), to let other content in the Activity consume
265             // the nav bar (ie, by using "fitSystemWindows")
266             if (BuildCompat.isAtLeastQ()) {
267                 WindowInsets.Builder builder = new WindowInsets.Builder(windowInsets);
268                 builder.setSystemWindowInsets(Insets.of(windowInsets.getSystemWindowInsetLeft(),
269                         0, windowInsets.getStableInsetRight(),
270                         windowInsets.getSystemWindowInsetBottom()));
271                 return builder.build();
272             } else {
273                 return windowInsets.replaceSystemWindowInsets(
274                         windowInsets.getSystemWindowInsetLeft(),
275                         0, windowInsets.getStableInsetRight(),
276                         windowInsets.getSystemWindowInsetBottom());
277             }
278         });
279 
280         // Set toolbar as the action bar.
281         Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
282         setSupportActionBar(toolbar);
283 
284         FragmentManager fm = getSupportFragmentManager();
285         Fragment fragment = fm.findFragmentById(R.id.fragment_container);
286 
287         if (fragment == null) {
288             // App launch specific logic: log the "app launched" event and set up daily logging.
289             mUserEventLogger.logAppLaunched();
290             DailyLoggingAlarmScheduler.setAlarm(getApplicationContext());
291 
292             CategoryFragment newFragment = CategoryFragment.newInstance(
293                     getString(R.string.wallpaper_app_name));
294             fm.beginTransaction()
295                     .add(R.id.fragment_container, newFragment)
296                     .commit();
297         }
298 
299         mDelegate.initialize(shouldForceRefresh);
300     }
301 
initializeDesktop(Bundle savedInstanceState)302     private void initializeDesktop(Bundle savedInstanceState) {
303         setContentView(R.layout.activity_top_level_desktop);
304 
305         mBottomSheet = (LinearLayout) findViewById(R.id.bottom_sheet);
306         mCurrentWallpaperImage = (ImageView) mBottomSheet.findViewById(R.id.current_wallpaper_image);
307         mCurrentWallpaperImage.getLayoutParams().width = getSingleWallpaperImageWidthPx();
308 
309         mCurrentWallpaperPresentationMode =
310                 (TextView) mBottomSheet.findViewById(R.id.current_wallpaper_presentation_mode);
311         mCurrentWallpaperTitle = (TextView) findViewById(R.id.current_wallpaper_title);
312         mCurrentWallpaperSubtitle = (TextView) findViewById(R.id.current_wallpaper_subtitle);
313         mCurrentWallpaperExploreButton = (Button) findViewById(
314                 R.id.current_wallpaper_explore_button);
315         mCurrentWallpaperSkipWallpaperButton = (Button) findViewById(
316                 R.id.current_wallpaper_skip_wallpaper_button);
317         mFragmentContainer = (FrameLayout) findViewById(R.id.fragment_container);
318         mLoadingIndicatorContainer = (FrameLayout) findViewById(R.id.loading_indicator_container);
319         mWallpaperPositionOptions = (LinearLayout) findViewById(
320                 R.id.desktop_wallpaper_position_options);
321 
322         final TabLayout tabLayout = (TabLayout) findViewById(R.id.tab_layout);
323         tabLayout.addOnTabSelectedListener(new OnTabSelectedListener() {
324             @Override
325             public void onTabSelected(Tab tab) {
326                 Category category = (Category) tab.getTag();
327                 showCategoryDesktop(category.getCollectionId());
328                 mLastSelectedCategoryTabIndex = tabLayout.getSelectedTabPosition();
329             }
330 
331             @Override
332             public void onTabUnselected(Tab tab) {
333             }
334 
335             @Override
336             public void onTabReselected(Tab tab) {
337                 Category category = (Category) tab.getTag();
338                 // If offline, "My photos" may be the only visible category. In this case we want to allow
339                 // re-selection so user can still select a photo as wallpaper while offline.
340                 if (!category.isEnumerable()) {
341                     onTabSelected(tab);
342                 }
343             }
344         });
345 
346         FragmentManager fm = getSupportFragmentManager();
347         Fragment fragment = fm.findFragmentById(R.id.fragment_container);
348 
349         if (fragment == null) {
350             // App launch specific logic: log the "app launched" event and set up daily logging.
351             mUserEventLogger.logAppLaunched();
352             DailyLoggingAlarmScheduler.setAlarm(getApplicationContext());
353         }
354 
355         mNetworkStatusListener = new NetworkStatusNotifier.Listener() {
356             @Override
357             public void onNetworkChanged(@NetworkStatus int networkStatus) {
358                 initializeDesktopBasedOnNetwork(networkStatus, savedInstanceState);
359             }
360         };
361         // Upon registering a listener, the onNetworkChanged method is immediately called with the
362         // initial network status.
363         mNetworkStatusNotifier.registerListener(mNetworkStatusListener);
364     }
365 
initializeDesktopBasedOnNetwork(@etworkStatus int networkStatus, Bundle savedInstanceState)366     private void initializeDesktopBasedOnNetwork(@NetworkStatus int networkStatus,
367                                                  Bundle savedInstanceState) {
368         if (networkStatus == NetworkStatusNotifier.NETWORK_CONNECTED) {
369             initializeDesktopOnline(savedInstanceState);
370         } else {
371             initializeDesktopOffline();
372         }
373     }
374 
initializeDesktopOnline(Bundle savedInstanceState)375     private void initializeDesktopOnline(Bundle savedInstanceState) {
376         FragmentManager fm = getSupportFragmentManager();
377         Fragment fragment = fm.findFragmentById(R.id.fragment_container);
378 
379         // Require a category refresh if this is the first load of the app or if the app is now
380         // returning online after having been offline.
381         boolean forceCategoryRefresh = fragment == null || fragment instanceof OfflineDesktopFragment;
382 
383         if (fragment != null) {
384             fm.beginTransaction()
385                     .remove(fragment)
386                     .commit();
387         }
388 
389         mLastSelectedCategoryTabIndex = savedInstanceState != null
390                 ? savedInstanceState.getInt(KEY_SELECTED_CATEGORY_TAB) : -1;
391         mDelegate.populateCategories(forceCategoryRefresh);
392 
393         setDesktopLoading(true);
394         setUpBottomSheet();
395         refreshCurrentWallpapers(null /* refreshListener */);
396     }
397 
initializeDesktopOffline()398     private void initializeDesktopOffline() {
399         FragmentManager fm = getSupportFragmentManager();
400         Fragment fragment = fm.findFragmentById(R.id.fragment_container);
401 
402         if (fragment != null) {
403             fm.beginTransaction()
404                     .remove(fragment)
405                     .commit();
406         }
407         OfflineDesktopFragment newFragment = new OfflineDesktopFragment();
408         fm.beginTransaction()
409                 .add(R.id.fragment_container, newFragment)
410                 .commit();
411 
412         // Reset the last selected category tab index to ensure the app doesn't try to reselect a
413         // tab for a category not yet repopulated.
414         mLastSelectedCategoryTabIndex = -1;
415 
416         mDelegate.populateCategories(true /* forceCategoryRefresh */);
417 
418         setDesktopLoading(false);
419         setCurrentWallpapersExpanded(false);
420     }
421 
422     /**
423      * Sets the status of the loading indicator overlay in desktop mode.
424      *
425      * @param loading Whether an indeterminate loading indicator is displayed in place of the main
426      *                fragment.
427      */
setDesktopLoading(boolean loading)428     private void setDesktopLoading(boolean loading) {
429         if (loading) {
430             mLoadingIndicatorContainer.setVisibility(View.VISIBLE);
431             mFragmentContainer.setVisibility(View.GONE);
432         } else {
433             mLoadingIndicatorContainer.setVisibility(View.GONE);
434             mFragmentContainer.setVisibility(View.VISIBLE);
435         }
436     }
437 
438     /**
439      * Returns the width (in physical px) to use for the "currently set wallpaper" thumbnail.
440      */
getSingleWallpaperImageWidthPx()441     private int getSingleWallpaperImageWidthPx() {
442         Point screenSize = ScreenSizeCalculator.getInstance().getScreenSize(
443                 getWindowManager().getDefaultDisplay());
444 
445         int height = getResources().getDimensionPixelSize(
446                 R.dimen.current_wallpaper_bottom_sheet_thumb_height);
447         return height * screenSize.x / screenSize.y;
448     }
449 
450     /**
451      * Enables and populates the "Currently set" wallpaper BottomSheet.
452      */
setUpBottomSheet()453     private void setUpBottomSheet() {
454         mBottomSheet.setVisibility(View.VISIBLE);
455 
456         if (Flags.skipDailyWallpaperButtonEnabled) {
457             // Add "next" icon to the Next Wallpaper button
458             Drawable nextWallpaperButtonDrawable = getResources().getDrawable(
459                     R.drawable.ic_refresh_18px);
460 
461             // This Drawable's state is shared across the app, so make a copy of it before applying a
462             // color tint as not to affect other clients elsewhere in the app.
463             nextWallpaperButtonDrawable =
464                     nextWallpaperButtonDrawable.getConstantState().newDrawable().mutate();
465             // Color the "compass" icon with the accent color.
466             nextWallpaperButtonDrawable.setColorFilter(
467                     getResources().getColor(R.color.accent_color), Mode.SRC_IN);
468             ButtonDrawableSetterCompat.setDrawableToButtonStart(
469                     mCurrentWallpaperSkipWallpaperButton, nextWallpaperButtonDrawable);
470         }
471 
472         final BottomSheetBehavior<LinearLayout> bottomSheetBehavior =
473                 BottomSheetBehavior.from(mBottomSheet);
474         bottomSheetBehavior.setBottomSheetCallback(new BottomSheetCallback() {
475             @Override
476             public void onStateChanged(@NonNull View view, int i) {
477             }
478 
479             @Override
480             public void onSlide(@NonNull View view, float slideOffset) {
481                 float alpha;
482                 if (slideOffset >= 0) {
483                     alpha = slideOffset;
484                 } else {
485                     alpha = 1f - slideOffset;
486                 }
487                 LinearLayout bottomSheetContents = findViewById(R.id.bottom_sheet_contents);
488                 bottomSheetContents.setAlpha(alpha);
489             }
490         });
491     }
492 
493     /**
494      * Enables a test mode of operation -- in which certain UI features are disabled to allow for
495      * UI tests to run correctly. Works around issue in ProgressDialog currently where the dialog
496      * constantly keeps the UI thread alive and blocks a test forever.
497      */
setTestingMode(boolean testingMode)498     void setTestingMode(boolean testingMode) {
499         mTestingMode = testingMode;
500     }
501 
502     /**
503      * Obtains the {@link WallpaperInfo} object(s) representing the wallpaper(s) currently set to the
504      * device from the {@link CurrentWallpaperInfoFactory} and displays them in the BottomSheet.
505      */
506     @Override
refreshCurrentWallpapers(@ullable RefreshListener refreshListener)507     public void refreshCurrentWallpapers(@Nullable RefreshListener refreshListener) {
508         final Injector injector = InjectorProvider.getInjector();
509         final Context appContext = getApplicationContext();
510 
511         CurrentWallpaperInfoFactory factory = injector.getCurrentWallpaperFactory(this);
512         factory.createCurrentWallpaperInfos(new WallpaperInfoCallback() {
513             @Override
514             public void onWallpaperInfoCreated(
515                     final WallpaperInfo homeWallpaper,
516                     @Nullable final WallpaperInfo lockWallpaper,
517                     @PresentationMode final int presentationMode) {
518 
519                 if (isDestroyed()) {
520                     return;
521                 }
522 
523                 // Fetch the home wallpaper's thumbnail asset asynchronously to work around expensive
524                 // method call to WallpaperManager#getWallpaperFile made from the CurrentWallpaperInfoVN
525                 // getAsset() method.
526                 AssetReceiver assetReceiver = (Asset thumbAsset) -> {
527                     if (isDestroyed()) {
528                         return;
529                     }
530 
531                     homeWallpaper.getThumbAsset(appContext).loadDrawableWithTransition(
532                             TopLevelPickerActivity.this,
533                             mCurrentWallpaperImage,
534                             200 /* transitionDurationMillis */,
535                             () -> {
536                                 if (refreshListener != null) {
537                                     refreshListener.onCurrentWallpaperRefreshed();
538                                 }
539                             },
540                             Color.TRANSPARENT);
541                 };
542                 new FetchThumbAssetTask(appContext, homeWallpaper, assetReceiver).executeOnExecutor(
543                         AsyncTask.THREAD_POOL_EXECUTOR);
544 
545                 mCurrentWallpaperPresentationMode.setText(
546                         AttributionFormatter.getHumanReadableWallpaperPresentationMode(
547                                 TopLevelPickerActivity.this, presentationMode));
548 
549                 List<String> attributions = homeWallpaper.getAttributions(appContext);
550                 if (attributions.size() > 0 && attributions.get(0) != null) {
551                     mCurrentWallpaperTitle.setText(attributions.get(0));
552                 }
553 
554                 mCurrentWallpaperSubtitle.setText(
555                         AttributionFormatter.formatWallpaperSubtitle(appContext, homeWallpaper));
556 
557                 final String actionUrl = homeWallpaper.getActionUrl(appContext);
558                 if (actionUrl != null && !actionUrl.isEmpty()) {
559                     Uri exploreUri = Uri.parse(actionUrl);
560 
561                     ExploreIntentChecker intentChecker = injector.getExploreIntentChecker(appContext);
562                     intentChecker.fetchValidActionViewIntent(exploreUri, (@Nullable Intent exploreIntent) -> {
563                         if (exploreIntent != null && !isDestroyed()) {
564                             // Set the icon for the button
565                             Drawable exploreButtonDrawable = getResources().getDrawable(
566                                     homeWallpaper.getActionIconRes(appContext));
567 
568                             // This Drawable's state is shared across the app, so make a copy of it
569                             // before applying a color tint as not to affect other clients elsewhere
570                             // in the app.
571                             exploreButtonDrawable = exploreButtonDrawable.getConstantState()
572                                     .newDrawable().mutate();
573                             // Color the "compass" icon with the accent color.
574                             exploreButtonDrawable.setColorFilter(
575                                     getResources().getColor(R.color.accent_color), Mode.SRC_IN);
576 
577                             ButtonDrawableSetterCompat.setDrawableToButtonStart(
578                                     mCurrentWallpaperExploreButton, exploreButtonDrawable);
579                             mCurrentWallpaperExploreButton.setText(getString(
580                                     homeWallpaper.getActionLabelRes(appContext)));
581                             mCurrentWallpaperExploreButton.setVisibility(View.VISIBLE);
582                             mCurrentWallpaperExploreButton.setOnClickListener(new OnClickListener() {
583                                 @Override
584                                 public void onClick(View v) {
585                                     mUserEventLogger.logActionClicked(
586                                             homeWallpaper.getCollectionId(appContext),
587                                             homeWallpaper.getActionLabelRes(appContext));
588                                     startActivity(exploreIntent);
589                                 }
590                             });
591                         }
592                     });
593                 } else {
594                     mCurrentWallpaperExploreButton.setVisibility(View.GONE);
595                 }
596 
597                 // Hide the wallpaper position options UI if the current home wallpaper is not from
598                 // "my photos".
599                 String homeCollectionId = homeWallpaper.getCollectionId(TopLevelPickerActivity.this);
600                 if (mWallpaperPositionOptions != null
601                         && homeCollectionId != null // May be null if app is being used for the first time.
602                         && !homeCollectionId.equals(getString(R.string.image_wallpaper_collection_id))) {
603                     mWallpaperPositionOptions.setVisibility(View.GONE);
604                 }
605 
606                 boolean showSkipWallpaperButton = Flags.skipDailyWallpaperButtonEnabled
607                         && presentationMode == WallpaperPreferences.PRESENTATION_MODE_ROTATING;
608                 if (showSkipWallpaperButton) {
609                     mCurrentWallpaperSkipWallpaperButton.setVisibility(View.VISIBLE);
610                     mCurrentWallpaperSkipWallpaperButton.setOnClickListener(
611                             v -> refreshDailyWallpaper());
612                 } else {
613                     mCurrentWallpaperSkipWallpaperButton.setVisibility(View.GONE);
614                 }
615 
616                 if (refreshListener != null) {
617                     refreshListener.onCurrentWallpaperRefreshed();
618                 }
619             }
620         }, true /* forceRefresh */);
621     }
622 
623     @Override
onSaveInstanceState(Bundle savedInstanceState)624     public void onSaveInstanceState(Bundle savedInstanceState) {
625         FormFactorChecker formFactorChecker = InjectorProvider.getInjector().getFormFactorChecker(this);
626         if (formFactorChecker.getFormFactor() == FormFactorChecker.FORM_FACTOR_DESKTOP) {
627             TabLayout tabLayout = (TabLayout) findViewById(R.id.tab_layout);
628 
629             // tabLayout is only present when the main IndividualPickerFragment is present (as
630             // opposed to
631             // the WallpaperDisabledFragment), so need this null check.
632             if (tabLayout != null) {
633                 savedInstanceState.putInt(KEY_SELECTED_CATEGORY_TAB, tabLayout.getSelectedTabPosition());
634             }
635         }
636 
637         super.onSaveInstanceState(savedInstanceState);
638     }
639 
640     @Override
641     @Nullable
getCategoryFragment()642     public CategoryFragment getCategoryFragment() {
643         if (mDelegate.getFormFactor() != FormFactorChecker.FORM_FACTOR_MOBILE) {
644             return null;
645         }
646         FragmentManager fm = getSupportFragmentManager();
647         return (CategoryFragment) fm.findFragmentById(R.id.fragment_container);
648     }
649 
650     /**
651      * Populates the category tabs on DESKTOP form factor.
652      *
653      * @param selectedTabPosition The position of the tab to show as selected, or -1 if no particular
654      *                            tab should be selected (in which case: the tab of the category for the currently set
655      *                            wallpaper will be selected if enumerable; if not, the first enumerable category's tab will
656      *                            be selected).
657      */
populateCategoryTabs(int selectedTabPosition)658     private void populateCategoryTabs(int selectedTabPosition) {
659         final TabLayout tabLayout = (TabLayout) findViewById(R.id.tab_layout);
660         tabLayout.removeAllTabs();
661 
662         String currentlySetCollectionId = mDelegate.getPreferences().getHomeWallpaperCollectionId();
663 
664         Tab tabToSelect = null;
665         Tab firstEnumerableCategoryTab = null;
666         for (int i = 0; i < mDelegate.getCategoryProvider().getSize(); i++) {
667             Category category = mDelegate.getCategoryProvider().getCategory(i);
668 
669             Tab tab = tabLayout.newTab();
670             tab.setText(category.getTitle());
671             tab.setTag(category);
672             tabLayout.addTab(tab, false /* setSelected */);
673 
674             if (firstEnumerableCategoryTab == null && category.isEnumerable()) {
675                 firstEnumerableCategoryTab = tab;
676             }
677 
678             boolean shouldSelectTab = (i == selectedTabPosition)
679                     || (selectedTabPosition == -1
680                     && tabToSelect == null
681                     && category.isEnumerable()
682                     && currentlySetCollectionId != null
683                     && currentlySetCollectionId.equals(category.getCollectionId()));
684 
685             if (shouldSelectTab) {
686                 tabToSelect = tab;
687             }
688         }
689 
690         // If the above loop did not identify a specific tab to select, then just select the tab for
691         // the first enumerable category.
692         if (tabToSelect == null) {
693             tabToSelect = firstEnumerableCategoryTab;
694         }
695 
696         // There may be no enumerable tabs (e.g., offline case), so we need to null-check again.
697         if (tabToSelect != null) {
698             tabToSelect.select();
699         }
700     }
701 
702     /**
703      * Refreshes the current wallpaper in a daily wallpaper rotation.
704      */
refreshDailyWallpaper()705     private void refreshDailyWallpaper() {
706         // ProgressDialog endlessly updates the UI thread, keeping it from going idle which therefore
707         // causes Espresso to hang once the dialog is shown.
708         if (!mTestingMode) {
709             int themeResId;
710             if (VERSION.SDK_INT < VERSION_CODES.LOLLIPOP) {
711                 themeResId = R.style.ProgressDialogThemePreL;
712             } else {
713                 themeResId = R.style.LightDialogTheme;
714             }
715             mRefreshWallpaperProgressDialog = new ProgressDialog(this, themeResId);
716             mRefreshWallpaperProgressDialog.setTitle(null);
717             mRefreshWallpaperProgressDialog.setMessage(
718                     getResources().getString(R.string.refreshing_daily_wallpaper_dialog_message));
719             mRefreshWallpaperProgressDialog.setIndeterminate(true);
720             mRefreshWallpaperProgressDialog.show();
721         }
722 
723         WallpaperRotationRefresher wallpaperRotationRefresher =
724                 InjectorProvider.getInjector().getWallpaperRotationRefresher();
725         wallpaperRotationRefresher.refreshWallpaper(this, new Listener() {
726             @Override
727             public void onRefreshed() {
728                 if (isDestroyed()) {
729                     return;
730                 }
731 
732                 if (mRefreshWallpaperProgressDialog != null) {
733                     mRefreshWallpaperProgressDialog.dismiss();
734                 }
735 
736                 refreshCurrentWallpapers(null /* refreshListener */);
737             }
738 
739             @Override
740             public void onError() {
741                 if (mRefreshWallpaperProgressDialog != null) {
742                     mRefreshWallpaperProgressDialog.dismiss();
743                 }
744 
745                 AlertDialog errorDialog = new AlertDialog.Builder(
746                         TopLevelPickerActivity.this, R.style.LightDialogTheme)
747                         .setMessage(R.string.refresh_daily_wallpaper_failed_message)
748                         .setPositiveButton(android.R.string.ok, null /* onClickListener */)
749                         .create();
750                 errorDialog.show();
751             }
752         });
753     }
754 
755     @Override
onActivityResult(int requestCode, int resultCode, Intent data)756     protected void onActivityResult(int requestCode, int resultCode, Intent data) {
757         super.onActivityResult(requestCode, resultCode, data);
758 
759         if (requestCode == WallpaperPickerDelegate.SHOW_CATEGORY_REQUEST_CODE
760                 && resultCode == Activity.RESULT_OK) {
761             if (mDelegate.getFormFactor() == FormFactorChecker.FORM_FACTOR_DESKTOP) {
762                 Uri imageUri = (data == null) ? null : data.getData();
763                 if (imageUri != null) {
764                     // User selected an image from the system picker, so launch the preview for that
765                     // image.
766                     ImageWallpaperInfo imageWallpaper = new ImageWallpaperInfo(imageUri);
767                     setCustomPhotoWallpaper(imageWallpaper);
768                     return;
769                 }
770             }
771         }
772         if (mDelegate.handleActivityResult(requestCode, resultCode, data)) {
773             finishActivityWithResultOk();
774         }
775     }
776 
777     /**
778      * Shows the view-only preview activity for the given wallpaper.
779      */
showViewOnlyPreview(WallpaperInfo wallpaperInfo)780     public void showViewOnlyPreview(WallpaperInfo wallpaperInfo) {
781         mDelegate.showViewOnlyPreview(wallpaperInfo);
782     }
783 
784     @Override
onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults)785     public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
786                                            @NonNull int[] grantResults) {
787         mDelegate.onRequestPermissionsResult(requestCode, permissions, grantResults);
788     }
789 
reselectLastTab()790     private void reselectLastTab() {
791         TabLayout tabLayout = (TabLayout) findViewById(R.id.tab_layout);
792 
793         // In the offline case, "My photos" could be the only category. Thus we need this check --
794         // to ensure that we don't try to select the "previously selected" category which was -1.
795         if (mLastSelectedCategoryTabIndex > -1) {
796             Tab tabToSelect = tabLayout.getTabAt(
797                     mLastSelectedCategoryTabIndex);
798             if (((Category) tabToSelect.getTag()).isEnumerable()) {
799                 tabToSelect.select();
800             }
801         }
802     }
803 
showCategoryDesktop(String collectionId)804     private void showCategoryDesktop(String collectionId) {
805         Category category = mDelegate.findCategoryForCollectionId(collectionId);
806         if (category == null) {
807             return;
808         }
809 
810         if (category.isEnumerable()) {
811             // Replace contained IndividualPickerFragment with a new instance for the given category.
812             final FragmentManager fm = getSupportFragmentManager();
813             Fragment fragment = fm.findFragmentById(R.id.fragment_container);
814             if (fragment != null) {
815                 fm.beginTransaction()
816                         .remove(fragment)
817                         .commit();
818             }
819             Injector injector = InjectorProvider.getInjector();
820             IndividualPickerFragment newFragment = injector.getIndividualPickerFragment(
821                     collectionId);
822             fm.beginTransaction()
823                     .add(R.id.fragment_container, newFragment)
824                     .commit();
825             newFragment.setCurrentWallpaperBottomSheetPresenter(this);
826             newFragment.setWallpapersUiContainer(this);
827         } else {
828             category.show(this, mDelegate.getPickerIntentFactory(),
829                     WallpaperPickerDelegate.SHOW_CATEGORY_REQUEST_CODE);
830 
831             // Need to select the tab here in case we are coming back from a "My photos" in which case
832             // the tab would have been set to "My photos" while viewing a regular image category.
833             reselectLastTab();
834         }
835     }
836 
finishActivityWithResultOk()837     private void finishActivityWithResultOk() {
838         overridePendingTransition(R.anim.fade_in, R.anim.fade_out);
839         setResult(Activity.RESULT_OK);
840         finish();
841     }
842 
843     @Override
setCurrentWallpapersExpanded(boolean expanded)844     public void setCurrentWallpapersExpanded(boolean expanded) {
845         final BottomSheetBehavior<LinearLayout> bottomSheetBehavior =
846                 BottomSheetBehavior.from(mBottomSheet);
847         bottomSheetBehavior.setState(
848                 expanded ? BottomSheetBehavior.STATE_EXPANDED
849                         : BottomSheetBehavior.STATE_COLLAPSED);
850     }
851 
852     @Override
doneFetchingCategories()853     public void doneFetchingCategories() {
854         populateCategoryTabs(mLastSelectedCategoryTabIndex);
855     }
856 
857     @Override
onWallpapersReady()858     public void onWallpapersReady() {
859         setDesktopLoading(false);
860         setCurrentWallpapersExpanded(true);
861     }
862 
863     @Override
getMyPhotosStarter()864     public MyPhotosStarter getMyPhotosStarter() {
865         return this;
866     }
867 
868     @Override
onClickTryAgain(@estination int unused)869     public void onClickTryAgain(@Destination int unused) {
870         // Retry the set wallpaper operation with the default center-crop setting.
871         if (mPendingSetWallpaperInfo != null) {
872             setCustomPhotoWallpaper(mPendingSetWallpaperInfo);
873         }
874     }
875 
876     /**
877      * Sets the provides wallpaper to the device with center-cropped and scaled to fit the device's
878      * default display.
879      */
setCustomPhotoWallpaper(final WallpaperInfo wallpaper)880     private void setCustomPhotoWallpaper(final WallpaperInfo wallpaper) {
881         // Save this WallpaperInfo so we can retry this operation later if it fails.
882         mPendingSetWallpaperInfo = wallpaper;
883 
884         showSettingWallpaperProgressDialog();
885 
886         mWallpaperPersister.setIndividualWallpaperWithPosition(this, wallpaper,
887                 WallpaperPersister.WALLPAPER_POSITION_CENTER_CROP, new SetWallpaperCallback() {
888                     @Override
889                     public void onSuccess(WallpaperInfo wallpaperInfo) {
890                         dismissSettingWallpaperProgressDialog();
891                         refreshCurrentWallpapers(null /* refreshListener */);
892 
893                         mDelegate.getPreferences().setPendingWallpaperSetStatus(
894                                 WallpaperPreferences.WALLPAPER_SET_NOT_PENDING);
895                         mUserEventLogger.logWallpaperSet(
896                                 wallpaper.getCollectionId(getApplicationContext()),
897                                 wallpaper.getWallpaperId());
898                         mUserEventLogger.logWallpaperSetResult(UserEventLogger.WALLPAPER_SET_RESULT_SUCCESS);
899 
900                         // The user may have closed the activity before the set wallpaper operation completed.
901                         if (isDestroyed()) {
902                             return;
903                         }
904 
905                         // Show the wallpaper crop option selector and bind click event handlers.
906                         mWallpaperPositionOptions.setVisibility(View.VISIBLE);
907 
908                         mWasCustomPhotoWallpaperSet = true;
909                         mCustomPhotoWallpaperPosition = WallpaperPersister.WALLPAPER_POSITION_CENTER_CROP;
910 
911                         initializeWallpaperPositionOptionClickHandlers(wallpaper);
912                     }
913 
914                     @Override
915                     public void onError(Throwable throwable) {
916                         dismissSettingWallpaperProgressDialog();
917                         showSetWallpaperErrorDialog();
918 
919                         mDelegate.getPreferences().setPendingWallpaperSetStatus(
920                                 WallpaperPreferences.WALLPAPER_SET_NOT_PENDING);
921                         mUserEventLogger.logWallpaperSetResult(
922                                 UserEventLogger.WALLPAPER_SET_RESULT_FAILURE);
923                         @WallpaperSetFailureReason int failureReason = ThrowableAnalyzer.isOOM(throwable)
924                                 ? UserEventLogger.WALLPAPER_SET_FAILURE_REASON_OOM
925                                 : UserEventLogger.WALLPAPER_SET_FAILURE_REASON_OTHER;
926                         mUserEventLogger.logWallpaperSetFailureReason(failureReason);
927                         Log.e(TAG, "Unable to set wallpaper from 'my photos'.");
928                     }
929                 });
930     }
931 
932     /**
933      * Initializes the wallpaper position button click handlers to change the way the provided
934      * wallpaper is set to the device.
935      */
initializeWallpaperPositionOptionClickHandlers(final WallpaperInfo wallpaperInfo)936     private void initializeWallpaperPositionOptionClickHandlers(final WallpaperInfo wallpaperInfo) {
937         Button centerCropOptionBtn = (Button) findViewById(R.id.wallpaper_position_option_center_crop);
938         Button stretchOptionBtn = (Button) findViewById(R.id.wallpaper_position_option_stretched);
939         Button centerOptionBtn = (Button) findViewById(R.id.wallpaper_position_option_center);
940 
941         // The "center crop" wallpaper position button is selected by default.
942         setCenterCropWallpaperPositionButtonSelected(centerCropOptionBtn, true /* isSelected */);
943         centerCropOptionBtn.setOnClickListener(new OnClickListener() {
944             @Override
945             public void onClick(View view) {
946                 mWallpaperPersister.setIndividualWallpaperWithPosition(TopLevelPickerActivity.this,
947                         wallpaperInfo, WallpaperPersister.WALLPAPER_POSITION_CENTER_CROP,
948                         new SetWallpaperCallback() {
949                             @Override
950                             public void onSuccess(WallpaperInfo wallpaperInfo) {
951                                 // The user may have closed the activity before the set wallpaper operation
952                                 // completed.
953                                 if (isDestroyed()) {
954                                     return;
955                                 }
956 
957                                 refreshCurrentWallpapers(null /* refreshListener */);
958 
959                                 setCenterCropWallpaperPositionButtonSelected(
960                                         centerCropOptionBtn, true /* isSelected */);
961                                 setCenterWallpaperPositionButtonSelected(centerOptionBtn, false /* isSelected */);
962                                 setStretchWallpaperPositionButtonSelected(stretchOptionBtn, false /* isSelected */);
963 
964                                 mCustomPhotoWallpaperPosition = WallpaperPersister.WALLPAPER_POSITION_CENTER_CROP;
965                             }
966 
967                             @Override
968                             public void onError(@Nullable Throwable throwable) {
969                                 // no-op
970                             }
971                         });
972             }
973         });
974 
975         // "Stretch" is not selected by default.
976         setStretchWallpaperPositionButtonSelected(stretchOptionBtn, false /* isSelected */);
977         stretchOptionBtn.setOnClickListener(new OnClickListener() {
978             @Override
979             public void onClick(View view) {
980                 mWallpaperPersister.setIndividualWallpaperWithPosition(TopLevelPickerActivity.this,
981                         wallpaperInfo, WallpaperPersister.WALLPAPER_POSITION_STRETCH,
982                         new SetWallpaperCallback() {
983                             @Override
984                             public void onSuccess(WallpaperInfo wallpaperInfo) {
985                                 // The user may have closed the activity before the set wallpaper operation
986                                 // completed.
987                                 if (isDestroyed()) {
988                                     return;
989                                 }
990 
991                                 refreshCurrentWallpapers(null /* refreshListener */);
992 
993                                 setStretchWallpaperPositionButtonSelected(stretchOptionBtn, true /* isSelected */);
994                                 setCenterCropWallpaperPositionButtonSelected(
995                                         centerCropOptionBtn, false /* isSelected */);
996                                 setCenterWallpaperPositionButtonSelected(centerOptionBtn, false /* isSelected */);
997 
998                                 mCustomPhotoWallpaperPosition = WallpaperPersister.WALLPAPER_POSITION_STRETCH;
999                             }
1000 
1001                             @Override
1002                             public void onError(@Nullable Throwable throwable) {
1003                                 // no-op
1004                             }
1005                         });
1006             }
1007         });
1008 
1009         // "Center" is not selected by default.
1010         setCenterWallpaperPositionButtonSelected(centerOptionBtn, false /* isSelected */);
1011         centerOptionBtn.setOnClickListener(new OnClickListener() {
1012             @Override
1013             public void onClick(View view) {
1014                 mWallpaperPersister.setIndividualWallpaperWithPosition(TopLevelPickerActivity.this,
1015                         wallpaperInfo, WallpaperPersister.WALLPAPER_POSITION_CENTER,
1016                         new SetWallpaperCallback() {
1017                             @Override
1018                             public void onSuccess(WallpaperInfo wallpaperInfo) {
1019                                 // The user may have closed the activity before the set wallpaper operation
1020                                 // completed.
1021                                 if (isDestroyed()) {
1022                                     return;
1023                                 }
1024 
1025                                 refreshCurrentWallpapers(null /* refreshListener */);
1026 
1027                                 setCenterWallpaperPositionButtonSelected(centerOptionBtn, true /* isSelected */);
1028                                 setCenterCropWallpaperPositionButtonSelected(
1029                                         centerCropOptionBtn, false /* isSelected */);
1030                                 setStretchWallpaperPositionButtonSelected(stretchOptionBtn, false /* isSelected */);
1031 
1032                                 mCustomPhotoWallpaperPosition = WallpaperPersister.WALLPAPER_POSITION_CENTER;
1033                             }
1034 
1035                             @Override
1036                             public void onError(@Nullable Throwable throwable) {
1037                                 // no-op
1038                             }
1039                         });
1040             }
1041         });
1042     }
1043 
setCenterWallpaperPositionButtonSelected(Button button, boolean isSelected)1044     private void setCenterWallpaperPositionButtonSelected(Button button, boolean isSelected) {
1045         int drawableId = isSelected ? R.drawable.center_blue : R.drawable.center_grey;
1046         ButtonDrawableSetterCompat.setDrawableToButtonStart(button, getDrawable(drawableId));
1047         button.setTextColor(getColor(getTextColorIdForWallpaperPositionButton(isSelected)));
1048     }
1049 
setCenterCropWallpaperPositionButtonSelected(Button button, boolean isSelected)1050     private void setCenterCropWallpaperPositionButtonSelected(Button button, boolean isSelected) {
1051         int drawableId = isSelected ? R.drawable.center_crop_blue : R.drawable.center_crop_grey;
1052         ButtonDrawableSetterCompat.setDrawableToButtonStart(button, getDrawable(drawableId));
1053         button.setTextColor(getColor(getTextColorIdForWallpaperPositionButton(isSelected)));
1054     }
1055 
setStretchWallpaperPositionButtonSelected(Button button, boolean isSelected)1056     private void setStretchWallpaperPositionButtonSelected(Button button, boolean isSelected) {
1057         int drawableId = isSelected ? R.drawable.stretch_blue : R.drawable.stretch_grey;
1058         ButtonDrawableSetterCompat.setDrawableToButtonStart(button, getDrawable(drawableId));
1059         button.setTextColor(getColor(getTextColorIdForWallpaperPositionButton(isSelected)));
1060     }
1061 
showSettingWallpaperProgressDialog()1062     private void showSettingWallpaperProgressDialog() {
1063         // ProgressDialog endlessly updates the UI thread, keeping it from going idle which
1064         // therefore causes Espresso to hang once the dialog is shown.
1065         if (!mTestingMode) {
1066             int themeResId;
1067             if (VERSION.SDK_INT < VERSION_CODES.LOLLIPOP) {
1068                 themeResId = R.style.ProgressDialogThemePreL;
1069             } else {
1070                 themeResId = R.style.LightDialogTheme;
1071             }
1072             mSetWallpaperProgressDialog = new ProgressDialog(this, themeResId);
1073             mSetWallpaperProgressDialog.setTitle(null);
1074             mSetWallpaperProgressDialog.setMessage(
1075                     getResources().getString(R.string.set_wallpaper_progress_message));
1076             mSetWallpaperProgressDialog.setIndeterminate(true);
1077             mSetWallpaperProgressDialog.show();
1078         }
1079     }
1080 
dismissSettingWallpaperProgressDialog()1081     private void dismissSettingWallpaperProgressDialog() {
1082         if (mSetWallpaperProgressDialog != null) {
1083             mSetWallpaperProgressDialog.dismiss();
1084         }
1085     }
1086 
showSetWallpaperErrorDialog()1087     private void showSetWallpaperErrorDialog() {
1088         SetWallpaperErrorDialogFragment dialogFragment = SetWallpaperErrorDialogFragment.newInstance(
1089                 R.string.set_wallpaper_error_message, WallpaperPersister.DEST_BOTH);
1090 
1091         if (isSafeToCommitFragmentTransaction()) {
1092             dialogFragment.show(getSupportFragmentManager(), TAG_SET_WALLPAPER_ERROR_DIALOG_FRAGMENT);
1093         } else {
1094             mStagedSetWallpaperErrorDialogFragment = dialogFragment;
1095         }
1096     }
1097 
1098     private interface AssetReceiver {
onAssetReceived(Asset asset)1099         void onAssetReceived(Asset asset);
1100     }
1101 
1102     /**
1103      * An AsyncTask for asynchronously fetching the thumbnail asset for a given WallpaperInfo.
1104      * Used to work around expensive method call to WallpaperManager#getWallpaperFile made from the
1105      * CurrentWallpaperInfoVN getAsset() method.
1106      */
1107     private static class FetchThumbAssetTask extends AsyncTask<Void, Void, Asset> {
1108         private Context mAppContext;
1109         private WallpaperInfo mWallpaperInfo;
1110         private AssetReceiver mReceiver;
1111 
FetchThumbAssetTask(Context appContext, WallpaperInfo wallpaperInfo, AssetReceiver receiver)1112         public FetchThumbAssetTask(Context appContext, WallpaperInfo wallpaperInfo,
1113                                    AssetReceiver receiver) {
1114             mAppContext = appContext;
1115             mWallpaperInfo = wallpaperInfo;
1116             mReceiver = receiver;
1117         }
1118 
1119         @Override
doInBackground(Void... params)1120         protected Asset doInBackground(Void... params) {
1121             return mWallpaperInfo.getThumbAsset(mAppContext);
1122         }
1123 
1124         @Override
onPostExecute(Asset thumbAsset)1125         protected void onPostExecute(Asset thumbAsset) {
1126             mReceiver.onAssetReceived(thumbAsset);
1127         }
1128     }
1129 }
1130