blob: 6353bc3a6cacb878006e5501dacb54b5d760b977 [file] [log] [blame]
Jon Miranda16ea1b12017-12-12 14:52:48 -08001/*
2 * Copyright (C) 2017 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16package com.android.wallpaper.picker;
17
18import android.Manifest.permission;
19import android.app.Activity;
20import android.app.ProgressDialog;
21import android.app.WallpaperManager;
22import android.content.Context;
23import android.content.Intent;
24import android.content.pm.PackageManager;
25import android.graphics.Color;
26import android.graphics.Point;
27import android.graphics.PorterDuff.Mode;
28import android.graphics.drawable.Drawable;
29import android.net.Uri;
30import android.os.AsyncTask;
31import android.os.Build.VERSION;
32import android.os.Build.VERSION_CODES;
33import android.os.Bundle;
Santiago Etchebehere1ee76a22018-05-15 15:02:24 -070034import android.service.wallpaper.WallpaperService;
Jon Miranda16ea1b12017-12-12 14:52:48 -080035import android.util.Log;
36import android.view.View;
37import android.view.View.OnClickListener;
38import android.widget.Button;
39import android.widget.FrameLayout;
40import android.widget.ImageView;
41import android.widget.LinearLayout;
42import android.widget.TextView;
43
44import com.android.wallpaper.R;
45import com.android.wallpaper.asset.Asset;
46import com.android.wallpaper.compat.ButtonDrawableSetterCompat;
47import com.android.wallpaper.compat.WallpaperManagerCompat;
48import com.android.wallpaper.config.Flags;
49import com.android.wallpaper.model.Category;
50import com.android.wallpaper.model.CategoryProvider;
51import com.android.wallpaper.model.CategoryReceiver;
52import com.android.wallpaper.model.ImageWallpaperInfo;
53import com.android.wallpaper.model.InlinePreviewIntentFactory;
54import com.android.wallpaper.model.WallpaperInfo;
55import com.android.wallpaper.module.CurrentWallpaperInfoFactory;
56import com.android.wallpaper.module.CurrentWallpaperInfoFactory.WallpaperInfoCallback;
57import com.android.wallpaper.module.DailyLoggingAlarmScheduler;
58import com.android.wallpaper.module.ExploreIntentChecker;
59import com.android.wallpaper.module.FormFactorChecker;
60import com.android.wallpaper.module.FormFactorChecker.FormFactor;
61import com.android.wallpaper.module.Injector;
62import com.android.wallpaper.module.InjectorProvider;
63import com.android.wallpaper.module.NetworkStatusNotifier;
64import com.android.wallpaper.module.NetworkStatusNotifier.NetworkStatus;
Santiago Etchebehere1ee76a22018-05-15 15:02:24 -070065import com.android.wallpaper.module.PackageStatusNotifier;
Jon Miranda16ea1b12017-12-12 14:52:48 -080066import com.android.wallpaper.module.UserEventLogger;
67import com.android.wallpaper.module.UserEventLogger.WallpaperSetFailureReason;
68import com.android.wallpaper.module.WallpaperPersister;
69import com.android.wallpaper.module.WallpaperPersister.Destination;
70import com.android.wallpaper.module.WallpaperPersister.SetWallpaperCallback;
71import com.android.wallpaper.module.WallpaperPersister.WallpaperPosition;
72import com.android.wallpaper.module.WallpaperPreferences;
73import com.android.wallpaper.module.WallpaperPreferences.PresentationMode;
74import com.android.wallpaper.module.WallpaperRotationRefresher;
75import com.android.wallpaper.module.WallpaperRotationRefresher.Listener;
76import com.android.wallpaper.picker.PreviewActivity.PreviewActivityIntentFactory;
77import com.android.wallpaper.picker.ViewOnlyPreviewActivity.ViewOnlyPreviewActivityIntentFactory;
78import com.android.wallpaper.picker.WallpaperDisabledFragment.WallpaperSupportLevel;
Sunny Goyal8600a3f2018-08-15 12:48:01 -070079import com.android.wallpaper.picker.individual.IndividualPickerActivity
80 .IndividualPickerActivityIntentFactory;
Jon Miranda16ea1b12017-12-12 14:52:48 -080081import com.android.wallpaper.picker.individual.IndividualPickerFragment;
82import com.android.wallpaper.util.ScreenSizeCalculator;
83import com.android.wallpaper.util.ThrowableAnalyzer;
84
Sunny Goyal8600a3f2018-08-15 12:48:01 -070085import com.google.android.material.bottomsheet.BottomSheetBehavior;
86import com.google.android.material.bottomsheet.BottomSheetBehavior.BottomSheetCallback;
87import com.google.android.material.tabs.TabLayout;
88import com.google.android.material.tabs.TabLayout.OnTabSelectedListener;
89import com.google.android.material.tabs.TabLayout.Tab;
90
Jon Miranda16ea1b12017-12-12 14:52:48 -080091import java.util.ArrayList;
92import java.util.List;
93
Sunny Goyal8600a3f2018-08-15 12:48:01 -070094import androidx.annotation.NonNull;
95import androidx.annotation.Nullable;
96import androidx.appcompat.app.AlertDialog;
97import androidx.appcompat.widget.Toolbar;
98import androidx.fragment.app.Fragment;
99import androidx.fragment.app.FragmentManager;
100
Jon Miranda16ea1b12017-12-12 14:52:48 -0800101/**
102 * Activity allowing users to select a category of wallpapers to choose from.
103 */
104public class TopLevelPickerActivity extends BaseActivity implements WallpapersUiContainer,
105 CurrentWallpaperBottomSheetPresenter, SetWallpaperErrorDialogFragment.Listener,
106 MyPhotosLauncher {
107 private static final int SHOW_CATEGORY_REQUEST_CODE = 0;
108 private static final int PREVIEW_WALLPAPER_REQUEST_CODE = 1;
109 private static final int VIEW_ONLY_PREVIEW_WALLPAPER_REQUEST_CODE = 2;
110 private static final int READ_EXTERNAL_STORAGE_PERMISSION_REQUEST_CODE = 3;
111
112 private static final String TAG_SET_WALLPAPER_ERROR_DIALOG_FRAGMENT =
113 "toplevel_set_wallpaper_error_dialog";
114
115 private static final String TAG = "TopLevelPicker";
116 private static final String KEY_SELECTED_CATEGORY_TAB = "selected_category_tab";
117
118 private IndividualPickerActivityIntentFactory mPickerIntentFactory;
119 private InlinePreviewIntentFactory mPreviewIntentFactory;
120 private InlinePreviewIntentFactory mViewOnlyPreviewIntentFactory;
Jon Miranda16ea1b12017-12-12 14:52:48 -0800121 private int mLastSelectedCategoryTabIndex;
122 @FormFactor
123 private int mFormFactor;
124 private WallpaperPreferences mPreferences;
125 private UserEventLogger mUserEventLogger;
126 private NetworkStatusNotifier mNetworkStatusNotifier;
127 private NetworkStatusNotifier.Listener mNetworkStatusListener;
Santiago Etchebehere1ee76a22018-05-15 15:02:24 -0700128 private PackageStatusNotifier mPackageStatusNotifier;
Jon Miranda16ea1b12017-12-12 14:52:48 -0800129 private WallpaperPersister mWallpaperPersister;
130 private boolean mWasCustomPhotoWallpaperSet;
131 @WallpaperPosition
132 private int mCustomPhotoWallpaperPosition;
133
134 /**
135 * Progress dialogs for "refresh daily wallpaper" and "set wallpaper" operations.
136 */
137 private ProgressDialog mRefreshWallpaperProgressDialog;
138 private ProgressDialog mSetWallpaperProgressDialog;
139
140 /**
141 * Designates a test mode of operation -- in which certain UI features are disabled to allow for
142 * UI tests to run correctly.
143 */
144 private boolean mTestingMode;
145
146 /**
147 * UI for the "currently set wallpaper" BottomSheet.
148 */
149 private LinearLayout mBottomSheet;
150 private ImageView mCurrentWallpaperImage;
151 private TextView mCurrentWallpaperPresentationMode;
152 private TextView mCurrentWallpaperTitle;
153 private TextView mCurrentWallpaperSubtitle;
154 private Button mCurrentWallpaperExploreButton;
155 private Button mCurrentWallpaperSkipWallpaperButton;
156 private FrameLayout mFragmentContainer;
157 private FrameLayout mLoadingIndicatorContainer;
158 private LinearLayout mWallpaperPositionOptions;
159
160 private List<PermissionChangedListener> mPermissionChangedListeners;
161
162 /**
163 * Staged error dialog fragments that were unable to be shown when the activity didn't allow
164 * committing fragment transactions.
165 */
166 private SetWallpaperErrorDialogFragment mStagedSetWallpaperErrorDialogFragment;
167
168 /**
169 * A wallpaper pending set to the device--we retain a reference to this in order to facilitate
170 * retry or re-crop operations.
171 */
172 private WallpaperInfo mPendingSetWallpaperInfo;
Santiago Etchebehere1ee76a22018-05-15 15:02:24 -0700173 private PackageStatusNotifier.Listener mLiveWallpaperStatusListener;
174 private PackageStatusNotifier.Listener mThirdPartyStatusListener;
175 private CategoryProvider mCategoryProvider;
Jon Miranda16ea1b12017-12-12 14:52:48 -0800176
177 private static int getTextColorIdForWallpaperPositionButton(boolean isSelected) {
178 return isSelected ? R.color.accent_color : R.color.material_grey500;
179 }
180
181 @Override
182 protected void onCreate(Bundle savedInstanceState) {
183 super.onCreate(savedInstanceState);
184
185 mPickerIntentFactory = new IndividualPickerActivityIntentFactory();
186 mPreviewIntentFactory = new PreviewActivityIntentFactory();
187 mViewOnlyPreviewIntentFactory = new ViewOnlyPreviewActivityIntentFactory();
Jon Miranda16ea1b12017-12-12 14:52:48 -0800188 mLastSelectedCategoryTabIndex = -1;
189
190 Injector injector = InjectorProvider.getInjector();
Santiago Etchebehere1ee76a22018-05-15 15:02:24 -0700191 mCategoryProvider = injector.getCategoryProvider(this);
Jon Miranda16ea1b12017-12-12 14:52:48 -0800192 mPreferences = injector.getPreferences(this);
193 mUserEventLogger = injector.getUserEventLogger(this);
194 mNetworkStatusNotifier = injector.getNetworkStatusNotifier(this);
Santiago Etchebehere1ee76a22018-05-15 15:02:24 -0700195 mPackageStatusNotifier = injector.getPackageStatusNotifier(this);
Jon Miranda16ea1b12017-12-12 14:52:48 -0800196 final FormFactorChecker formFactorChecker = injector.getFormFactorChecker(this);
197 mFormFactor = formFactorChecker.getFormFactor();
198 mWallpaperPersister = injector.getWallpaperPersister(this);
199 mWasCustomPhotoWallpaperSet = false;
200
201 mPermissionChangedListeners = new ArrayList<>();
202
203 @WallpaperSupportLevel int wallpaperSupportLevel = getWallpaperSupportLevel();
204 if (wallpaperSupportLevel != WallpaperDisabledFragment.SUPPORTED_CAN_SET) {
205 setContentView(R.layout.activity_single_fragment);
206
207 FragmentManager fm = getSupportFragmentManager();
208 WallpaperDisabledFragment wallpaperDisabledFragment =
209 WallpaperDisabledFragment.newInstance(wallpaperSupportLevel);
210 fm.beginTransaction()
211 .add(R.id.fragment_container, wallpaperDisabledFragment)
212 .commit();
213 return;
214 }
215
216 if (mFormFactor == FormFactorChecker.FORM_FACTOR_MOBILE) {
217 initializeMobile();
218 } else { // DESKTOP
219 initializeDesktop(savedInstanceState);
220 }
221 }
222
223 @Override
224 protected void onResume() {
225 super.onResume();
226
227 // Show the staged 'load wallpaper' or 'set wallpaper' error dialog fragments if there is one
228 // that was unable to be shown earlier when this fragment's hosting activity didn't allow
229 // committing fragment transactions.
230 if (mStagedSetWallpaperErrorDialogFragment != null) {
231 mStagedSetWallpaperErrorDialogFragment.show(
232 getSupportFragmentManager(), TAG_SET_WALLPAPER_ERROR_DIALOG_FRAGMENT);
233 mStagedSetWallpaperErrorDialogFragment = null;
234 }
235 }
236
237 @Override
238 protected void onDestroy() {
239 super.onDestroy();
240
241 if (mNetworkStatusListener != null) {
242 mNetworkStatusNotifier.unregisterListener(mNetworkStatusListener);
243 }
244
Santiago Etchebehere1ee76a22018-05-15 15:02:24 -0700245 if (mPackageStatusNotifier != null) {
246 mPackageStatusNotifier.removeListener(mLiveWallpaperStatusListener);
247 mPackageStatusNotifier.removeListener(mThirdPartyStatusListener);
248 }
249
Jon Miranda16ea1b12017-12-12 14:52:48 -0800250 if (mRefreshWallpaperProgressDialog != null) {
251 mRefreshWallpaperProgressDialog.dismiss();
252 }
253 if (mSetWallpaperProgressDialog != null) {
254 mSetWallpaperProgressDialog.dismiss();
255 }
256
257 if (mFormFactor == FormFactorChecker.FORM_FACTOR_DESKTOP && mWasCustomPhotoWallpaperSet) {
258 mUserEventLogger.logWallpaperPosition(mCustomPhotoWallpaperPosition);
259 }
260 }
261
262 @Override
263 public void requestCustomPhotoPicker(PermissionChangedListener listener) {
264 if (!isReadExternalStoragePermissionGranted()) {
265 PermissionChangedListener wrappedListener = new PermissionChangedListener() {
266 @Override
267 public void onPermissionsGranted() {
268 listener.onPermissionsGranted();
269 showCustomPhotoPicker();
270 }
271
272 @Override
273 public void onPermissionsDenied(boolean dontAskAgain) {
274 listener.onPermissionsDenied(dontAskAgain);
275 }
276 };
277 requestExternalStoragePermission(wrappedListener);
278
279 return;
280 }
281
282 showCustomPhotoPicker();
283 }
284
285 void requestExternalStoragePermission(PermissionChangedListener listener) {
286 mPermissionChangedListeners.add(listener);
287 requestPermissions(
288 new String[]{permission.READ_EXTERNAL_STORAGE},
289 READ_EXTERNAL_STORAGE_PERMISSION_REQUEST_CODE);
290 }
291
292 /**
293 * Returns whether READ_EXTERNAL_STORAGE has been granted for the application.
294 */
295 boolean isReadExternalStoragePermissionGranted() {
296 return getPackageManager().checkPermission(permission.READ_EXTERNAL_STORAGE,
297 getPackageName()) == PackageManager.PERMISSION_GRANTED;
298 }
299
300 private void showCustomPhotoPicker() {
301 Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
302 intent.setType("image/*");
303 startActivityForResult(intent, SHOW_CATEGORY_REQUEST_CODE);
304 }
305
306 private void initializeMobile() {
307 setContentView(R.layout.activity_single_fragment_with_toolbar);
308
309 // Set toolbar as the action bar.
310 Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
311 setSupportActionBar(toolbar);
312
313 FragmentManager fm = getSupportFragmentManager();
314 Fragment fragment = fm.findFragmentById(R.id.fragment_container);
315
316 boolean forceCategoryRefresh = false;
317 if (fragment == null) {
318 // App launch specific logic: log the "app launched" event and set up daily logging.
319 mUserEventLogger.logAppLaunched();
320 DailyLoggingAlarmScheduler.setAlarm(getApplicationContext());
321
322 CategoryPickerFragment newFragment = new CategoryPickerFragment();
323 fm.beginTransaction()
324 .add(R.id.fragment_container, newFragment)
325 .commit();
326
327 forceCategoryRefresh = true;
328 }
329
330 populateCategories(-1, forceCategoryRefresh);
Santiago Etchebehere1ee76a22018-05-15 15:02:24 -0700331 mLiveWallpaperStatusListener = this::updateLiveWallpapersCategories;
332 mThirdPartyStatusListener = this::updateThirdPartyCategories;
333 mPackageStatusNotifier.addListener(mLiveWallpaperStatusListener,
334 WallpaperService.SERVICE_INTERFACE);
335 mPackageStatusNotifier.addListener(mThirdPartyStatusListener,
336 Intent.ACTION_SET_WALLPAPER);
337 }
338
339 private void updateThirdPartyCategories(String packageName, @PackageStatusNotifier.PackageStatus
340 int status) {
341
342 if (status == PackageStatusNotifier.PackageStatus.ADDED) {
343 mCategoryProvider.fetchCategories(new CategoryReceiver() {
344 @Override
345 public void onCategoryReceived(Category category) {
346 if (category.supportsThirdParty() && category.containsThirdParty(packageName)) {
347 addCategory(category, false);
348 }
349 }
350
351 @Override
352 public void doneFetchingCategories() {
353 // Do nothing here.
354 }
355 }, true);
356 } else if (status == PackageStatusNotifier.PackageStatus.REMOVED) {
357 Category oldCategory = findThirdPartyCategory(packageName);
358 if (oldCategory != null) {
359 mCategoryProvider.fetchCategories(new CategoryReceiver() {
360 @Override
361 public void onCategoryReceived(Category category) {
362 // Do nothing here
363 }
364
365 @Override
366 public void doneFetchingCategories() {
367 removeCategory(oldCategory);
368 }
369 }, true);
370 }
371 } else {
372 // CHANGED package, let's reload all categories as we could have more or fewer now
373 populateCategories(-1, true);
374 }
375 }
376
377 private Category findThirdPartyCategory(String packageName) {
378 int size = mCategoryProvider.getSize();
379 for (int i = 0; i < size; i++) {
380 Category category = mCategoryProvider.getCategory(i);
381 if (category.supportsThirdParty() && category.containsThirdParty(packageName)) {
382 return category;
383 }
384 }
385 return null;
386 }
387
388 private void updateLiveWallpapersCategories(String packageName,
389 @PackageStatusNotifier.PackageStatus int status) {
390 String liveWallpaperCollectionId = getString(R.string.live_wallpaper_collection_id);
391 Category oldLiveWallpapersCategory = mCategoryProvider.getCategory(
392 liveWallpaperCollectionId);
393 if (status == PackageStatusNotifier.PackageStatus.REMOVED
394 && (oldLiveWallpapersCategory == null
395 || !oldLiveWallpapersCategory.containsThirdParty(packageName))) {
396 // If we're removing a wallpaper and the live category didn't contain it already,
397 // there's nothing to do.
398 return;
399 }
400 mCategoryProvider.fetchCategories(new CategoryReceiver() {
401 @Override
402 public void onCategoryReceived(Category category) {
403 // Do nothing here
404 }
405
406 @Override
407 public void doneFetchingCategories() {
408 Category liveWallpapersCategory =
409 mCategoryProvider.getCategory(liveWallpaperCollectionId);
410 if (liveWallpapersCategory == null) {
411 // There are no more 3rd party live wallpapers, so the Category is gone.
412 removeCategory(oldLiveWallpapersCategory);
413 } else {
414 if (oldLiveWallpapersCategory != null) {
415 updateCategory(liveWallpapersCategory);
416 } else {
417 addCategory(liveWallpapersCategory, false);
418 }
419 }
420 }
421 }, true);
Jon Miranda16ea1b12017-12-12 14:52:48 -0800422 }
423
424 private void initializeDesktop(Bundle savedInstanceState) {
425 setContentView(R.layout.activity_top_level_desktop);
426
427 mBottomSheet = (LinearLayout) findViewById(R.id.bottom_sheet);
428 mCurrentWallpaperImage = (ImageView) mBottomSheet.findViewById(R.id.current_wallpaper_image);
429 mCurrentWallpaperImage.getLayoutParams().width = getSingleWallpaperImageWidthPx();
430
431 mCurrentWallpaperPresentationMode =
432 (TextView) mBottomSheet.findViewById(R.id.current_wallpaper_presentation_mode);
433 mCurrentWallpaperTitle = (TextView) findViewById(R.id.current_wallpaper_title);
434 mCurrentWallpaperSubtitle = (TextView) findViewById(R.id.current_wallpaper_subtitle);
435 mCurrentWallpaperExploreButton = (Button) findViewById(
436 R.id.current_wallpaper_explore_button);
437 mCurrentWallpaperSkipWallpaperButton = (Button) findViewById(
438 R.id.current_wallpaper_skip_wallpaper_button);
439 mFragmentContainer = (FrameLayout) findViewById(R.id.fragment_container);
440 mLoadingIndicatorContainer = (FrameLayout) findViewById(R.id.loading_indicator_container);
441 mWallpaperPositionOptions = (LinearLayout) findViewById(
442 R.id.desktop_wallpaper_position_options);
443
444 final TabLayout tabLayout = (TabLayout) findViewById(R.id.tab_layout);
445 tabLayout.addOnTabSelectedListener(new OnTabSelectedListener() {
446 @Override
447 public void onTabSelected(Tab tab) {
448 Category category = (Category) tab.getTag();
449 show(category.getCollectionId());
450 mLastSelectedCategoryTabIndex = tabLayout.getSelectedTabPosition();
451 }
452
453 @Override
454 public void onTabUnselected(Tab tab) {
455 }
456
457 @Override
458 public void onTabReselected(Tab tab) {
459 Category category = (Category) tab.getTag();
460 // If offline, "My photos" may be the only visible category. In this case we want to allow
461 // re-selection so user can still select a photo as wallpaper while offline.
462 if (!category.isEnumerable()) {
463 onTabSelected(tab);
464 }
465 }
466 });
467
468 FragmentManager fm = getSupportFragmentManager();
469 Fragment fragment = fm.findFragmentById(R.id.fragment_container);
470
471 if (fragment == null) {
472 // App launch specific logic: log the "app launched" event and set up daily logging.
473 mUserEventLogger.logAppLaunched();
474 DailyLoggingAlarmScheduler.setAlarm(getApplicationContext());
475 }
476
477 mNetworkStatusListener = new NetworkStatusNotifier.Listener() {
478 @Override
479 public void onNetworkChanged(@NetworkStatus int networkStatus) {
480 initializeDesktopBasedOnNetwork(networkStatus, savedInstanceState);
481 }
482 };
483 // Upon registering a listener, the onNetworkChanged method is immediately called with the
484 // initial network status.
485 mNetworkStatusNotifier.registerListener(mNetworkStatusListener);
486 }
487
488 private void initializeDesktopBasedOnNetwork(@NetworkStatus int networkStatus,
489 Bundle savedInstanceState) {
490 if (networkStatus == NetworkStatusNotifier.NETWORK_CONNECTED) {
491 initializeDesktopOnline(savedInstanceState);
492 } else {
493 initializeDesktopOffline();
494 }
495 }
496
497 private void initializeDesktopOnline(Bundle savedInstanceState) {
498 FragmentManager fm = getSupportFragmentManager();
499 Fragment fragment = fm.findFragmentById(R.id.fragment_container);
500
501 // Require a category refresh if this is the first load of the app or if the app is now
502 // returning online after having been offline.
503 boolean forceCategoryRefresh = fragment == null || fragment instanceof OfflineDesktopFragment;
504
505 if (fragment != null) {
506 fm.beginTransaction()
507 .remove(fragment)
508 .commit();
509 }
510
511 int selectedTabPosition = savedInstanceState != null
512 ? savedInstanceState.getInt(KEY_SELECTED_CATEGORY_TAB) : -1;
513 populateCategories(selectedTabPosition, forceCategoryRefresh);
514
515 setDesktopLoading(true);
516 setUpBottomSheet();
517 refreshCurrentWallpapers(null /* refreshListener */);
518 }
519
520 private void initializeDesktopOffline() {
521 FragmentManager fm = getSupportFragmentManager();
522 Fragment fragment = fm.findFragmentById(R.id.fragment_container);
523
524 if (fragment != null) {
525 fm.beginTransaction()
526 .remove(fragment)
527 .commit();
528 }
529 OfflineDesktopFragment newFragment = new OfflineDesktopFragment();
530 fm.beginTransaction()
531 .add(R.id.fragment_container, newFragment)
532 .commit();
533
534 // Reset the last selected category tab index to ensure the app doesn't try to reselect a tab
535 // for a category not yet repopulated.
536 mLastSelectedCategoryTabIndex = -1;
537
538 populateCategories(-1 /* selectedTabPosition */, true /* forceCategoryRefresh */);
539
540 setDesktopLoading(false);
541 setCurrentWallpapersExpanded(false);
542 }
543
544 /**
545 * Sets the status of the loading indicator overlay in desktop mode.
546 *
547 * @param loading Whether an indeterminate loading indicator is displayed in place of the main
548 * fragment.
549 */
550 private void setDesktopLoading(boolean loading) {
551 if (loading) {
552 mLoadingIndicatorContainer.setVisibility(View.VISIBLE);
553 mFragmentContainer.setVisibility(View.GONE);
554 } else {
555 mLoadingIndicatorContainer.setVisibility(View.GONE);
556 mFragmentContainer.setVisibility(View.VISIBLE);
557 }
558 }
559
560 /**
561 * Returns the width (in physical px) to use for the "currently set wallpaper" thumbnail.
562 */
563 private int getSingleWallpaperImageWidthPx() {
564 Point screenSize = ScreenSizeCalculator.getInstance().getScreenSize(
565 getWindowManager().getDefaultDisplay());
566
567 int height = getResources().getDimensionPixelSize(
568 R.dimen.current_wallpaper_bottom_sheet_thumb_height);
569 return height * screenSize.x / screenSize.y;
570 }
571
572 /**
573 * Enables and populates the "Currently set" wallpaper BottomSheet.
574 */
575 private void setUpBottomSheet() {
576 mBottomSheet.setVisibility(View.VISIBLE);
577
Jon Miranda16ea1b12017-12-12 14:52:48 -0800578 if (Flags.skipDailyWallpaperButtonEnabled) {
579 // Add "next" icon to the Next Wallpaper button
580 Drawable nextWallpaperButtonDrawable = getResources().getDrawable(
Santiago Etchebehered24506c2018-04-05 17:02:42 -0700581 R.drawable.ic_refresh_18px);
Jon Miranda16ea1b12017-12-12 14:52:48 -0800582
583 // This Drawable's state is shared across the app, so make a copy of it before applying a
584 // color tint as not to affect other clients elsewhere in the app.
585 nextWallpaperButtonDrawable =
586 nextWallpaperButtonDrawable.getConstantState().newDrawable().mutate();
587 // Color the "compass" icon with the accent color.
588 nextWallpaperButtonDrawable.setColorFilter(
589 getResources().getColor(R.color.accent_color), Mode.SRC_IN);
590 ButtonDrawableSetterCompat.setDrawableToButtonStart(
591 mCurrentWallpaperSkipWallpaperButton, nextWallpaperButtonDrawable);
592 }
593
594 final BottomSheetBehavior<LinearLayout> bottomSheetBehavior =
595 BottomSheetBehavior.from(mBottomSheet);
596 bottomSheetBehavior.setBottomSheetCallback(new BottomSheetCallback() {
597 @Override
598 public void onStateChanged(@NonNull View view, int i) {
599 }
600
601 @Override
602 public void onSlide(@NonNull View view, float slideOffset) {
603 float alpha;
604 if (slideOffset >= 0) {
605 alpha = slideOffset;
606 } else {
607 alpha = 1f - slideOffset;
608 }
609 LinearLayout bottomSheetContents = (LinearLayout) findViewById(R.id.bottom_sheet_contents);
610 bottomSheetContents.setAlpha(alpha);
611 }
612 });
613 }
614
615 /**
616 * Enables a test mode of operation -- in which certain UI features are disabled to allow for
617 * UI tests to run correctly. Works around issue in ProgressDialog currently where the dialog
618 * constantly keeps the UI thread alive and blocks a test forever.
619 */
620 void setTestingMode(boolean testingMode) {
621 mTestingMode = testingMode;
622 }
623
624 /**
625 * Obtains the {@link WallpaperInfo} object(s) representing the wallpaper(s) currently set to the
626 * device from the {@link CurrentWallpaperInfoFactory} and displays them in the BottomSheet.
627 */
628 @Override
629 public void refreshCurrentWallpapers(@Nullable RefreshListener refreshListener) {
630 final Injector injector = InjectorProvider.getInjector();
631 final Context appContext = getApplicationContext();
632
633 CurrentWallpaperInfoFactory factory = injector.getCurrentWallpaperFactory(this);
634 factory.createCurrentWallpaperInfos(new WallpaperInfoCallback() {
635 @Override
636 public void onWallpaperInfoCreated(
637 final WallpaperInfo homeWallpaper,
638 @Nullable final WallpaperInfo lockWallpaper,
639 @PresentationMode final int presentationMode) {
640
641 if (isDestroyed()) {
642 return;
643 }
644
645 // Fetch the home wallpaper's thumbnail asset asynchronously to work around expensive
646 // method call to WallpaperManager#getWallpaperFile made from the CurrentWallpaperInfoVN
647 // getAsset() method.
648 AssetReceiver assetReceiver = (Asset thumbAsset) -> {
649 if (isDestroyed()) {
650 return;
651 }
652
653 homeWallpaper.getThumbAsset(appContext).loadDrawableWithTransition(
654 TopLevelPickerActivity.this,
655 mCurrentWallpaperImage,
656 200 /* transitionDurationMillis */,
657 () -> {
658 if (refreshListener != null) {
659 refreshListener.onCurrentWallpaperRefreshed();
660 }
661 },
662 Color.TRANSPARENT);
663 };
664 new FetchThumbAssetTask(appContext, homeWallpaper, assetReceiver).executeOnExecutor(
665 AsyncTask.THREAD_POOL_EXECUTOR);
666
667 mCurrentWallpaperPresentationMode.setText(
668 AttributionFormatter.getHumanReadableWallpaperPresentationMode(
669 TopLevelPickerActivity.this, presentationMode));
670
671 List<String> attributions = homeWallpaper.getAttributions(appContext);
672 if (attributions.size() > 0 && attributions.get(0) != null) {
673 mCurrentWallpaperTitle.setText(attributions.get(0));
674 }
675
676 mCurrentWallpaperSubtitle.setText(
677 AttributionFormatter.formatWallpaperSubtitle(appContext, homeWallpaper));
678
679 final String actionUrl = homeWallpaper.getActionUrl(appContext);
680 if (actionUrl != null && !actionUrl.isEmpty()) {
681 Uri exploreUri = Uri.parse(actionUrl);
682
683 ExploreIntentChecker intentChecker = injector.getExploreIntentChecker(appContext);
684 intentChecker.fetchValidActionViewIntent(exploreUri, (@Nullable Intent exploreIntent) -> {
685 if (exploreIntent != null && !isDestroyed()) {
Santiago Etchebehered1bd5092018-04-18 16:03:30 -0700686 // Set the icon for the button
687 Drawable exploreButtonDrawable = getResources().getDrawable(
Santiago Etchebeheree0810d02018-05-10 17:39:40 -0700688 homeWallpaper.getActionIconRes(appContext));
Santiago Etchebehered1bd5092018-04-18 16:03:30 -0700689
690 // This Drawable's state is shared across the app, so make a copy of it
691 // before applying a color tint as not to affect other clients elsewhere
692 // in the app.
693 exploreButtonDrawable = exploreButtonDrawable.getConstantState()
694 .newDrawable().mutate();
695 // Color the "compass" icon with the accent color.
696 exploreButtonDrawable.setColorFilter(
697 getResources().getColor(R.color.accent_color), Mode.SRC_IN);
698
699 ButtonDrawableSetterCompat.setDrawableToButtonStart(
700 mCurrentWallpaperExploreButton, exploreButtonDrawable);
701 mCurrentWallpaperExploreButton.setText(getString(
Santiago Etchebeheree0810d02018-05-10 17:39:40 -0700702 homeWallpaper.getActionLabelRes(appContext)));
Jon Miranda16ea1b12017-12-12 14:52:48 -0800703 mCurrentWallpaperExploreButton.setVisibility(View.VISIBLE);
704 mCurrentWallpaperExploreButton.setOnClickListener(new OnClickListener() {
705 @Override
706 public void onClick(View v) {
Santiago Etchebeherece5613f2018-06-01 13:22:47 -0700707 mUserEventLogger.logActionClicked(
708 homeWallpaper.getCollectionId(appContext),
709 homeWallpaper.getActionLabelRes(appContext));
Jon Miranda16ea1b12017-12-12 14:52:48 -0800710 startActivity(exploreIntent);
711 }
712 });
713 }
714 });
715 } else {
716 mCurrentWallpaperExploreButton.setVisibility(View.GONE);
717 }
718
719 // Hide the wallpaper position options UI if the current home wallpaper is not from
720 // "my photos".
721 String homeCollectionId = homeWallpaper.getCollectionId(TopLevelPickerActivity.this);
722 if (mWallpaperPositionOptions != null
723 && homeCollectionId != null // May be null if app is being used for the first time.
724 && !homeCollectionId.equals(getString(R.string.image_wallpaper_collection_id))) {
725 mWallpaperPositionOptions.setVisibility(View.GONE);
726 }
727
728 boolean showSkipWallpaperButton = Flags.skipDailyWallpaperButtonEnabled
729 && presentationMode == WallpaperPreferences.PRESENTATION_MODE_ROTATING;
730 if (showSkipWallpaperButton) {
731 mCurrentWallpaperSkipWallpaperButton.setVisibility(View.VISIBLE);
732 mCurrentWallpaperSkipWallpaperButton.setOnClickListener(new OnClickListener() {
733 @Override
734 public void onClick(View v) {
735 refreshDailyWallpaper();
736 }
737 });
738 } else {
739 mCurrentWallpaperSkipWallpaperButton.setVisibility(View.GONE);
740 }
741
742 if (refreshListener != null) {
743 refreshListener.onCurrentWallpaperRefreshed();
744 }
745 }
746 }, true /* forceRefresh */);
747 }
748
749 @Override
750 public void onSaveInstanceState(Bundle savedInstanceState) {
751 FormFactorChecker formFactorChecker = InjectorProvider.getInjector().getFormFactorChecker(this);
752 if (formFactorChecker.getFormFactor() == FormFactorChecker.FORM_FACTOR_DESKTOP) {
753 TabLayout tabLayout = (TabLayout) findViewById(R.id.tab_layout);
754
755 // tabLayout is only present when the main IndividualPickerFragment is present (as opposed to
756 // the WallpaperDisabledFragment), so need this null check.
757 if (tabLayout != null) {
758 savedInstanceState.putInt(KEY_SELECTED_CATEGORY_TAB, tabLayout.getSelectedTabPosition());
759 }
760 }
761
762 super.onSaveInstanceState(savedInstanceState);
763 }
764
765 /**
766 * Populates the categories appropriately depending on the device form factor.
767 *
768 * @param selectedTabPosition The position of the tab to show as selected, or -1 if no tab
769 * should be selected (i.e. because there is no tab layout present, as on MOBILE form factor).
770 * @param forceRefresh Whether to force a refresh of categories from the CategoryProvider. True if
771 * on first launch.
772 */
773 private void populateCategories(final int selectedTabPosition, boolean forceRefresh) {
Jon Miranda16ea1b12017-12-12 14:52:48 -0800774
Santiago Etchebehere1ee76a22018-05-15 15:02:24 -0700775 final CategoryPickerFragment categoryPickerFragment = getCategoryPickerFragment();
776
777 if (forceRefresh && categoryPickerFragment != null) {
778 categoryPickerFragment.clearCategories();
779 }
780
781 mCategoryProvider.fetchCategories(new CategoryReceiver() {
Jon Miranda16ea1b12017-12-12 14:52:48 -0800782 @Override
783 public void onCategoryReceived(Category category) {
Santiago Etchebehere1ee76a22018-05-15 15:02:24 -0700784 addCategory(category, true);
Jon Miranda16ea1b12017-12-12 14:52:48 -0800785 }
786
787 @Override
788 public void doneFetchingCategories() {
789 if (mFormFactor == FormFactorChecker.FORM_FACTOR_MOBILE) {
Santiago Etchebehere1ee76a22018-05-15 15:02:24 -0700790 notifyDoneFetchingCategories();
Jon Miranda16ea1b12017-12-12 14:52:48 -0800791 } else { // DESKTOP
792 populateCategoryTabs(selectedTabPosition);
793 }
794 }
795 }, forceRefresh);
796 }
797
Santiago Etchebehere1ee76a22018-05-15 15:02:24 -0700798 private void notifyDoneFetchingCategories() {
799 CategoryPickerFragment categoryPickerFragment = getCategoryPickerFragment();
800 if (categoryPickerFragment != null) {
801 categoryPickerFragment.doneFetchingCategories();
802 }
803 }
804
805 private void addCategory(Category category, boolean fetchingAll) {
806 CategoryPickerFragment categoryPickerFragment = getCategoryPickerFragment();
807 if (categoryPickerFragment != null) {
808 categoryPickerFragment.addCategory(category, fetchingAll);
809 }
810 }
811
812 private void removeCategory(Category category) {
813 CategoryPickerFragment categoryPickerFragment = getCategoryPickerFragment();
814 if (categoryPickerFragment != null) {
815 categoryPickerFragment.removeCategory(category);
816 }
817 }
818
819 private void updateCategory(Category category) {
820 CategoryPickerFragment categoryPickerFragment = getCategoryPickerFragment();
821 if (categoryPickerFragment != null) {
822 categoryPickerFragment.updateCategory(category);
823 }
824 }
825
826 @Nullable
827 private CategoryPickerFragment getCategoryPickerFragment() {
828 if (mFormFactor != FormFactorChecker.FORM_FACTOR_MOBILE) {
829 return null;
830 }
831 FragmentManager fm = getSupportFragmentManager();
832 return (CategoryPickerFragment) fm.findFragmentById(R.id.fragment_container);
833 }
834
Jon Miranda16ea1b12017-12-12 14:52:48 -0800835 /**
836 * Populates the category tabs on DESKTOP form factor.
837 *
838 * @param selectedTabPosition The position of the tab to show as selected, or -1 if no particular
839 * tab should be selected (in which case: the tab of the category for the currently set
840 * wallpaper will be selected if enumerable; if not, the first enumerable category's tab will
841 * be selected).
842 */
843 private void populateCategoryTabs(int selectedTabPosition) {
844 final TabLayout tabLayout = (TabLayout) findViewById(R.id.tab_layout);
845 tabLayout.removeAllTabs();
846
847 String currentlySetCollectionId = mPreferences.getHomeWallpaperCollectionId();
848
849 Tab tabToSelect = null;
850 Tab firstEnumerableCategoryTab = null;
Santiago Etchebehere1ee76a22018-05-15 15:02:24 -0700851 for (int i = 0; i < mCategoryProvider.getSize(); i++) {
852 Category category = mCategoryProvider.getCategory(i);
Jon Miranda16ea1b12017-12-12 14:52:48 -0800853
854 Tab tab = tabLayout.newTab();
855 tab.setText(category.getTitle());
856 tab.setTag(category);
857 tabLayout.addTab(tab, false /* setSelected */);
858
859 if (firstEnumerableCategoryTab == null && category.isEnumerable()) {
860 firstEnumerableCategoryTab = tab;
861 }
862
863 boolean shouldSelectTab = (i == selectedTabPosition)
864 || (selectedTabPosition == -1
865 && tabToSelect == null
866 && category.isEnumerable()
867 && currentlySetCollectionId != null
868 && currentlySetCollectionId.equals(category.getCollectionId()));
869
870 if (shouldSelectTab) {
871 tabToSelect = tab;
872 }
873 }
874
875 // If the above loop did not identify a specific tab to select, then just select the tab for
876 // the first enumerable category.
877 if (tabToSelect == null) {
878 tabToSelect = firstEnumerableCategoryTab;
879 }
880
881 // There may be no enumerable tabs (e.g., offline case), so we need to null-check again.
882 if (tabToSelect != null) {
883 tabToSelect.select();
884 }
885 }
886
887 /**
888 * Refreshes the current wallpaper in a daily wallpaper rotation.
889 */
890 private void refreshDailyWallpaper() {
891 // ProgressDialog endlessly updates the UI thread, keeping it from going idle which therefore
892 // causes Espresso to hang once the dialog is shown.
893 if (!mTestingMode) {
894 int themeResId;
895 if (VERSION.SDK_INT < VERSION_CODES.LOLLIPOP) {
896 themeResId = R.style.ProgressDialogThemePreL;
897 } else {
898 themeResId = R.style.LightDialogTheme;
899 }
900 mRefreshWallpaperProgressDialog = new ProgressDialog(this, themeResId);
901 mRefreshWallpaperProgressDialog.setTitle(null);
902 mRefreshWallpaperProgressDialog.setMessage(
903 getResources().getString(R.string.refreshing_daily_wallpaper_dialog_message));
904 mRefreshWallpaperProgressDialog.setIndeterminate(true);
905 mRefreshWallpaperProgressDialog.show();
906 }
907
908 WallpaperRotationRefresher wallpaperRotationRefresher =
909 InjectorProvider.getInjector().getWallpaperRotationRefresher();
910 wallpaperRotationRefresher.refreshWallpaper(this, new Listener() {
911 @Override
912 public void onRefreshed() {
913 if (isDestroyed()) {
914 return;
915 }
916
917 if (mRefreshWallpaperProgressDialog != null) {
918 mRefreshWallpaperProgressDialog.dismiss();
919 }
920
921 refreshCurrentWallpapers(null /* refreshListener */);
922 }
923
924 @Override
925 public void onError() {
926 if (mRefreshWallpaperProgressDialog != null) {
927 mRefreshWallpaperProgressDialog.dismiss();
928 }
929
930 AlertDialog errorDialog = new AlertDialog.Builder(
931 TopLevelPickerActivity.this, R.style.LightDialogTheme)
932 .setMessage(R.string.refresh_daily_wallpaper_failed_message)
933 .setPositiveButton(android.R.string.ok, null /* onClickListener */)
934 .create();
935 errorDialog.show();
936 }
937 });
938 }
939
940 @Override
941 protected void onActivityResult(int requestCode, int resultCode, Intent data) {
942 super.onActivityResult(requestCode, resultCode, data);
943
944 if (requestCode == SHOW_CATEGORY_REQUEST_CODE && resultCode == Activity.RESULT_OK) {
945 Uri imageUri = (data == null) ? null : data.getData();
946 if (imageUri != null) {
947 // User selected an image from the system picker, so launch the preview for that image.
948 ImageWallpaperInfo imageWallpaper = new ImageWallpaperInfo(imageUri);
949 if (mFormFactor == FormFactorChecker.FORM_FACTOR_DESKTOP) {
950 setCustomPhotoWallpaper(imageWallpaper);
951 return;
952 }
953
954 imageWallpaper.showPreview(this, mPreviewIntentFactory, PREVIEW_WALLPAPER_REQUEST_CODE);
955 } else {
956 // User finished viewing a category without any data, which implies that the user previewed
957 // and selected a wallpaper in-app, so finish this activity.
958 finishActivityWithResultOk();
959 }
960 } else if (requestCode == PREVIEW_WALLPAPER_REQUEST_CODE && resultCode == Activity.RESULT_OK) {
961 // User previewed and selected a wallpaper, so finish this activity.
962 finishActivityWithResultOk();
963 }
964 }
965
966 /**
967 * Shows the view-only preview activity for the given wallpaper.
968 */
969 public void showViewOnlyPreview(WallpaperInfo wallpaperInfo) {
970 wallpaperInfo.showPreview(
971 this, mViewOnlyPreviewIntentFactory, VIEW_ONLY_PREVIEW_WALLPAPER_REQUEST_CODE);
972 }
973
974 @Override
975 public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
976 @NonNull int[] grantResults) {
977 if (requestCode == READ_EXTERNAL_STORAGE_PERMISSION_REQUEST_CODE
978 && permissions.length > 0
979 && permissions[0].equals(permission.READ_EXTERNAL_STORAGE)
980 && grantResults.length > 0) {
981 if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
982 for (PermissionChangedListener listener : mPermissionChangedListeners) {
983 listener.onPermissionsGranted();
984 }
985 } else if (!shouldShowRequestPermissionRationale(permission.READ_EXTERNAL_STORAGE)) {
986 for (PermissionChangedListener listener : mPermissionChangedListeners) {
987 listener.onPermissionsDenied(true /* dontAskAgain */);
988 }
989 } else {
990 for (PermissionChangedListener listener : mPermissionChangedListeners) {
991 listener.onPermissionsDenied(false /* dontAskAgain */);
992 }
993 }
994 }
995 mPermissionChangedListeners.clear();
996 }
997
998 /**
999 * Shows the picker activity for the given category.
1000 */
1001 public void show(String collectionId) {
1002 Category category = findCategoryForCollectionId(collectionId);
1003 if (category == null) {
1004 return;
1005 }
1006
1007 if (mFormFactor == FormFactorChecker.FORM_FACTOR_MOBILE) {
1008 category.show(this, mPickerIntentFactory, SHOW_CATEGORY_REQUEST_CODE);
1009 } else { // DESKTOP
1010 showCategoryDesktop(collectionId);
1011 }
1012 }
1013
1014 private void reselectLastTab() {
1015 TabLayout tabLayout = (TabLayout) findViewById(R.id.tab_layout);
1016
1017 // In the offline case, "My photos" could be the only category. Thus we need this check --
1018 // to ensure that we don't try to select the "previously selected" category which was -1.
1019 if (mLastSelectedCategoryTabIndex > -1) {
1020 Tab tabToSelect = tabLayout.getTabAt(mLastSelectedCategoryTabIndex);
1021 if (((Category) tabToSelect.getTag()).isEnumerable()) {
1022 tabToSelect.select();
1023 }
1024 }
1025 }
1026
1027 @Nullable
1028 private Category findCategoryForCollectionId(String collectionId) {
Santiago Etchebehere1ee76a22018-05-15 15:02:24 -07001029 return mCategoryProvider.getCategory(collectionId);
Jon Miranda16ea1b12017-12-12 14:52:48 -08001030 }
1031
1032 private void showCategoryDesktop(String collectionId) {
1033 Category category = findCategoryForCollectionId(collectionId);
1034 if (category == null) {
1035 return;
1036 }
1037
1038 if (category.isEnumerable()) {
1039 // Replace contained IndividualPickerFragment with a new instance for the given category.
1040 final FragmentManager fm = getSupportFragmentManager();
1041 Fragment fragment = fm.findFragmentById(R.id.fragment_container);
1042 if (fragment != null) {
1043 fm.beginTransaction()
1044 .remove(fragment)
1045 .commit();
1046 }
1047 IndividualPickerFragment newFragment = IndividualPickerFragment.newInstance(collectionId);
1048 fm.beginTransaction()
1049 .add(R.id.fragment_container, newFragment)
1050 .commit();
1051 newFragment.setCurrentWallpaperBottomSheetPresenter(this);
1052 newFragment.setWallpapersUiContainer(this);
1053 } else {
1054 category.show(this, mPickerIntentFactory, SHOW_CATEGORY_REQUEST_CODE);
1055
1056 // Need to select the tab here in case we are coming back from a "My photos" in which case
1057 // the tab would have been set to "My photos" while viewing a regular image category.
1058 reselectLastTab();
1059 }
1060 }
1061
1062 private void finishActivityWithResultOk() {
1063 overridePendingTransition(R.anim.fade_in, R.anim.fade_out);
1064 setResult(Activity.RESULT_OK);
1065 finish();
1066 }
1067
1068 @WallpaperSupportLevel
1069 private int getWallpaperSupportLevel() {
1070 WallpaperManager wallpaperManager = WallpaperManager.getInstance(this);
1071
1072 if (VERSION.SDK_INT >= VERSION_CODES.N) {
1073 if (wallpaperManager.isWallpaperSupported()) {
1074 return wallpaperManager.isSetWallpaperAllowed()
1075 ? WallpaperDisabledFragment.SUPPORTED_CAN_SET
1076 : WallpaperDisabledFragment.NOT_SUPPORTED_BLOCKED_BY_ADMIN;
1077 }
1078 return WallpaperDisabledFragment.NOT_SUPPORTED_BY_DEVICE;
1079 } else if (VERSION.SDK_INT >= VERSION_CODES.M) {
1080 return wallpaperManager.isWallpaperSupported() ? WallpaperDisabledFragment.SUPPORTED_CAN_SET
1081 : WallpaperDisabledFragment.NOT_SUPPORTED_BY_DEVICE;
1082 } else {
1083 WallpaperManagerCompat wallpaperManagerCompat =
1084 InjectorProvider.getInjector().getWallpaperManagerCompat(this);
1085 boolean isSupported = wallpaperManagerCompat.getDrawable() != null;
1086 wallpaperManager.forgetLoadedWallpaper();
1087 return isSupported ? WallpaperDisabledFragment.SUPPORTED_CAN_SET
1088 : WallpaperDisabledFragment.NOT_SUPPORTED_BY_DEVICE;
1089 }
1090 }
1091
1092 @Override
1093 public void setCurrentWallpapersExpanded(boolean expanded) {
1094 final BottomSheetBehavior<LinearLayout> bottomSheetBehavior =
1095 BottomSheetBehavior.from(mBottomSheet);
1096 bottomSheetBehavior.setState(
1097 expanded ? BottomSheetBehavior.STATE_EXPANDED : BottomSheetBehavior.STATE_COLLAPSED);
1098 }
1099
1100 @Override
1101 public void onWallpapersReady() {
1102 setDesktopLoading(false);
1103 setCurrentWallpapersExpanded(true);
1104 }
1105
1106 @Override
1107 public void onClickTryAgain(@Destination int unused) {
1108 // Retry the set wallpaper operation with the default center-crop setting.
1109 if (mPendingSetWallpaperInfo != null) {
1110 setCustomPhotoWallpaper(mPendingSetWallpaperInfo);
1111 }
1112 }
1113
1114 /**
1115 * Sets the provides wallpaper to the device with center-cropped and scaled to fit the device's
1116 * default display.
1117 */
1118 private void setCustomPhotoWallpaper(final WallpaperInfo wallpaper) {
1119 // Save this WallpaperInfo so we can retry this operation later if it fails.
1120 mPendingSetWallpaperInfo = wallpaper;
1121
1122 showSettingWallpaperProgressDialog();
1123
1124 mWallpaperPersister.setIndividualWallpaperWithPosition(this, wallpaper,
1125 WallpaperPersister.WALLPAPER_POSITION_CENTER_CROP, new SetWallpaperCallback() {
1126 @Override
1127 public void onSuccess() {
1128 dismissSettingWallpaperProgressDialog();
1129 refreshCurrentWallpapers(null /* refreshListener */);
1130
1131 mPreferences.setPendingWallpaperSetStatus(
1132 WallpaperPreferences.WALLPAPER_SET_NOT_PENDING);
1133 mUserEventLogger.logWallpaperSet(
1134 wallpaper.getCollectionId(getApplicationContext()),
1135 wallpaper.getWallpaperId());
1136 mUserEventLogger.logWallpaperSetResult(UserEventLogger.WALLPAPER_SET_RESULT_SUCCESS);
1137
1138 // The user may have closed the activity before the set wallpaper operation completed.
1139 if (isDestroyed()) {
1140 return;
1141 }
1142
1143 // Show the wallpaper crop option selector and bind click event handlers.
1144 mWallpaperPositionOptions.setVisibility(View.VISIBLE);
1145
1146 mWasCustomPhotoWallpaperSet = true;
1147 mCustomPhotoWallpaperPosition = WallpaperPersister.WALLPAPER_POSITION_CENTER_CROP;
1148
1149 initializeWallpaperPositionOptionClickHandlers(wallpaper);
1150 }
1151
1152 @Override
1153 public void onError(Throwable throwable) {
1154 dismissSettingWallpaperProgressDialog();
1155 showSetWallpaperErrorDialog();
1156
1157 mPreferences.setPendingWallpaperSetStatus(
1158 WallpaperPreferences.WALLPAPER_SET_NOT_PENDING);
1159 mUserEventLogger.logWallpaperSetResult(
1160 UserEventLogger.WALLPAPER_SET_RESULT_FAILURE);
1161 @WallpaperSetFailureReason int failureReason = ThrowableAnalyzer.isOOM(throwable)
1162 ? UserEventLogger.WALLPAPER_SET_FAILURE_REASON_OOM
1163 : UserEventLogger.WALLPAPER_SET_FAILURE_REASON_OTHER;
1164 mUserEventLogger.logWallpaperSetFailureReason(failureReason);
1165 Log.e(TAG, "Unable to set wallpaper from 'my photos'.");
1166 }
1167 });
1168 }
1169
1170 /**
1171 * Initializes the wallpaper position button click handlers to change the way the provided
1172 * wallpaper is set to the device.
1173 */
1174 private void initializeWallpaperPositionOptionClickHandlers(final WallpaperInfo wallpaperInfo) {
1175 Button centerCropOptionBtn = (Button) findViewById(R.id.wallpaper_position_option_center_crop);
1176 Button stretchOptionBtn = (Button) findViewById(R.id.wallpaper_position_option_stretched);
1177 Button centerOptionBtn = (Button) findViewById(R.id.wallpaper_position_option_center);
1178
1179 // The "center crop" wallpaper position button is selected by default.
1180 setCenterCropWallpaperPositionButtonSelected(centerCropOptionBtn, true /* isSelected */);
1181 centerCropOptionBtn.setOnClickListener(new OnClickListener() {
1182 @Override
1183 public void onClick(View view) {
1184 mWallpaperPersister.setIndividualWallpaperWithPosition(TopLevelPickerActivity.this,
1185 wallpaperInfo, WallpaperPersister.WALLPAPER_POSITION_CENTER_CROP,
1186 new SetWallpaperCallback() {
1187 @Override
1188 public void onSuccess() {
1189 // The user may have closed the activity before the set wallpaper operation
1190 // completed.
1191 if (isDestroyed()) {
1192 return;
1193 }
1194
1195 refreshCurrentWallpapers(null /* refreshListener */);
1196
1197 setCenterCropWallpaperPositionButtonSelected(
1198 centerCropOptionBtn, true /* isSelected */);
1199 setCenterWallpaperPositionButtonSelected(centerOptionBtn, false /* isSelected */);
1200 setStretchWallpaperPositionButtonSelected(stretchOptionBtn, false /* isSelected */);
1201
1202 mCustomPhotoWallpaperPosition = WallpaperPersister.WALLPAPER_POSITION_CENTER_CROP;
1203 }
1204
1205 @Override
1206 public void onError(@Nullable Throwable throwable) {
1207 // no-op
1208 }
1209 });
1210 }
1211 });
1212
1213 // "Stretch" is not selected by default.
1214 setStretchWallpaperPositionButtonSelected(stretchOptionBtn, false /* isSelected */);
1215 stretchOptionBtn.setOnClickListener(new OnClickListener() {
1216 @Override
1217 public void onClick(View view) {
1218 mWallpaperPersister.setIndividualWallpaperWithPosition(TopLevelPickerActivity.this,
1219 wallpaperInfo, WallpaperPersister.WALLPAPER_POSITION_STRETCH,
1220 new SetWallpaperCallback() {
1221 @Override
1222 public void onSuccess() {
1223 // The user may have closed the activity before the set wallpaper operation
1224 // completed.
1225 if (isDestroyed()) {
1226 return;
1227 }
1228
1229 refreshCurrentWallpapers(null /* refreshListener */);
1230
1231 setStretchWallpaperPositionButtonSelected(stretchOptionBtn, true /* isSelected */);
1232 setCenterCropWallpaperPositionButtonSelected(
1233 centerCropOptionBtn, false /* isSelected */);
1234 setCenterWallpaperPositionButtonSelected(centerOptionBtn, false /* isSelected */);
1235
1236 mCustomPhotoWallpaperPosition = WallpaperPersister.WALLPAPER_POSITION_STRETCH;
1237 }
1238
1239 @Override
1240 public void onError(@Nullable Throwable throwable) {
1241 // no-op
1242 }
1243 });
1244 }
1245 });
1246
1247 // "Center" is not selected by default.
1248 setCenterWallpaperPositionButtonSelected(centerOptionBtn, false /* isSelected */);
1249 centerOptionBtn.setOnClickListener(new OnClickListener() {
1250 @Override
1251 public void onClick(View view) {
1252 mWallpaperPersister.setIndividualWallpaperWithPosition(TopLevelPickerActivity.this,
1253 wallpaperInfo, WallpaperPersister.WALLPAPER_POSITION_CENTER,
1254 new SetWallpaperCallback() {
1255 @Override
1256 public void onSuccess() {
1257 // The user may have closed the activity before the set wallpaper operation
1258 // completed.
1259 if (isDestroyed()) {
1260 return;
1261 }
1262
1263 refreshCurrentWallpapers(null /* refreshListener */);
1264
1265 setCenterWallpaperPositionButtonSelected(centerOptionBtn, true /* isSelected */);
1266 setCenterCropWallpaperPositionButtonSelected(
1267 centerCropOptionBtn, false /* isSelected */);
1268 setStretchWallpaperPositionButtonSelected(stretchOptionBtn, false /* isSelected */);
1269
1270 mCustomPhotoWallpaperPosition = WallpaperPersister.WALLPAPER_POSITION_CENTER;
1271 }
1272
1273 @Override
1274 public void onError(@Nullable Throwable throwable) {
1275 // no-op
1276 }
1277 });
1278 }
1279 });
1280 }
1281
1282 private void setCenterWallpaperPositionButtonSelected(Button button, boolean isSelected) {
1283 int drawableId = isSelected ? R.drawable.center_blue : R.drawable.center_grey;
1284 ButtonDrawableSetterCompat.setDrawableToButtonStart(button, getDrawable(drawableId));
1285 button.setTextColor(getColor(getTextColorIdForWallpaperPositionButton(isSelected)));
1286 }
1287
1288 private void setCenterCropWallpaperPositionButtonSelected(Button button, boolean isSelected) {
1289 int drawableId = isSelected ? R.drawable.center_crop_blue : R.drawable.center_crop_grey;
1290 ButtonDrawableSetterCompat.setDrawableToButtonStart(button, getDrawable(drawableId));
1291 button.setTextColor(getColor(getTextColorIdForWallpaperPositionButton(isSelected)));
1292 }
1293
1294 private void setStretchWallpaperPositionButtonSelected(Button button, boolean isSelected) {
1295 int drawableId = isSelected ? R.drawable.stretch_blue : R.drawable.stretch_grey;
1296 ButtonDrawableSetterCompat.setDrawableToButtonStart(button, getDrawable(drawableId));
1297 button.setTextColor(getColor(getTextColorIdForWallpaperPositionButton(isSelected)));
1298 }
1299
1300 private void showSettingWallpaperProgressDialog() {
1301 // ProgressDialog endlessly updates the UI thread, keeping it from going idle which therefore
1302 // causes Espresso to hang once the dialog is shown.
1303 if (!mTestingMode) {
1304 int themeResId;
1305 if (VERSION.SDK_INT < VERSION_CODES.LOLLIPOP) {
1306 themeResId = R.style.ProgressDialogThemePreL;
1307 } else {
1308 themeResId = R.style.LightDialogTheme;
1309 }
1310 mSetWallpaperProgressDialog = new ProgressDialog(this, themeResId);
1311 mSetWallpaperProgressDialog.setTitle(null);
1312 mSetWallpaperProgressDialog.setMessage(
1313 getResources().getString(R.string.set_wallpaper_progress_message));
1314 mSetWallpaperProgressDialog.setIndeterminate(true);
1315 mSetWallpaperProgressDialog.show();
1316 }
1317 }
1318
1319 private void dismissSettingWallpaperProgressDialog() {
1320 if (mSetWallpaperProgressDialog != null) {
1321 mSetWallpaperProgressDialog.dismiss();
1322 }
1323 }
1324
1325 private void showSetWallpaperErrorDialog() {
1326 SetWallpaperErrorDialogFragment dialogFragment = SetWallpaperErrorDialogFragment.newInstance(
1327 R.string.set_wallpaper_error_message, WallpaperPersister.DEST_BOTH);
1328
1329 if (isSafeToCommitFragmentTransaction()) {
1330 dialogFragment.show(getSupportFragmentManager(), TAG_SET_WALLPAPER_ERROR_DIALOG_FRAGMENT);
1331 } else {
1332 mStagedSetWallpaperErrorDialogFragment = dialogFragment;
1333 }
1334 }
1335
1336 private interface AssetReceiver {
1337 void onAssetReceived(Asset asset);
1338 }
1339
1340 /**
1341 * An AsyncTask for asynchronously fetching the thumbnail asset for a given WallpaperInfo.
1342 * Used to work around expensive method call to WallpaperManager#getWallpaperFile made from the
1343 * CurrentWallpaperInfoVN getAsset() method.
1344 */
1345 private static class FetchThumbAssetTask extends AsyncTask<Void, Void, Asset> {
1346 private Context mAppContext;
1347 private WallpaperInfo mWallpaperInfo;
1348 private AssetReceiver mReceiver;
1349
1350 public FetchThumbAssetTask(Context appContext, WallpaperInfo wallpaperInfo,
1351 AssetReceiver receiver) {
1352 mAppContext = appContext;
1353 mWallpaperInfo = wallpaperInfo;
1354 mReceiver = receiver;
1355 }
1356
1357 @Override
1358 protected Asset doInBackground(Void... params) {
1359 return mWallpaperInfo.getThumbAsset(mAppContext);
1360 }
1361
1362 @Override
1363 protected void onPostExecute(Asset thumbAsset) {
1364 mReceiver.onAssetReceived(thumbAsset);
1365 }
1366 }
1367}