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