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